Java 大数据开发程序员大数据

Java大数据开发(一)- 搜索引擎 Lucene

2017-05-23  本文已影响1666人  SawyerZh
Lucene

写在前面:本文中用到的 Apache Lucene 版本号是 4.10.2 截止到文章发布时官方的最新版本是 6.5.1 因不同的版本差异较大,请大家在学习过程中确认下版本号是否一致。本文中的所涉及到的源码分享在在 Gighub 链接地址:Part01_Lucene

1.搜索引擎

1.1 - 概述

1.2 - 搜索原理

搜索引擎原理

1.3 - 应用场景

  1. 大型综合搜索网站
  2. 站内搜索
  3. 垂直领域搜索
  4. 软甲内部搜索

1.4 - 搜索技术

1.5 - 倒排索引

根据词条查找文档

文档编号 ID Title
0 1 谷歌地图之父跳槽Facebook
1 2 谷歌地图之父加盟Facebook
2 3 谷歌地图创始人拉斯离开谷歌加盟Facebook
3 4 谷歌地图之父跳槽Facebook与Wave项目取消有关
4 5 谷歌地图之父拉斯加盟社交网站Facebook
词条ID 词条 倒排列表(包含该词条文档 ID)
1 谷歌 0,1,2,3,4
2 地图 0,1,2,3,4
3 之父 0,1,3,4
4 跳槽 0,3
5 Facebook 0,1,2,3,4
6 加盟 1,2,4
7 创始人 2
8 拉斯 2,4
9 离开 2
10 3
11 wave 3
12 项目 3
13 取消 3
14 有关 3
15 社交 4
16 网站 4

2.Lucene

Apache Lucene

Apache Lucene

2.1 - 概述

2.2 - 全文检索

3.QuickStart

3.1 - 创建索引流程图

Lucene创建索引流程图
  1. 创建文档对象(Document),并添加索引Field字段(Field)

    • 索引字段:Field
  2. 创建目录对象(Directory)并指定索引在硬盘中存储位置

  3. 创建分词器对象(Analyzer)

  4. 创建索引写出器配置对象(IndexWriterConfig)

    • 索引分词器:Analyzer
    • 版本:Version
  5. 创建索引写出器(IndexWriter)

    • 目录:Directory
    • 索引写出器配置:IndexWriterConfig
  6. 索引写出器,添加文档对象

    • 文档:Document
  7. 提交并关闭索引写出器

3.2 - 创建索引

3.3 - 使用 lukeall 工具查看索引

LuceneData

4.创建索引详解

4.1 - Document

Document详解图

4.2 - Field

Field
  1. 存储 :StoreField 支持(byte[]、BytesRef、double、float、int、long、String)
  2. 创建索引 + 可选存储 :DoubleField、FloatField、IntField、LongField、StringField
  3. 创建索引 + 可选存储 + 分词 :TestField

4.3 - Directory

Directory

4.4 - Analyzer

Analyzer

4.5 - IndexWriterConfig

public IndexWriterConfig setOpenMode(OpenMode openMode)

public static enum OpenMode {
    /** 
    * Creates a new index or overwrites an existing one. 
    */
    CREATE,
        
    /** 
    * Opens an existing index. 
    */
    APPEND,
        
    /** 
    * Creates a new index if one does not exist,
    * otherwise it opens the index and documents will be appended. 
    */
    CREATE_OR_APPEND 
}

4.6 - IndexWriter

public void addDocuments(Iterable<? extends Iterable<? extends IndexableField>> docs)

5.查询索引

5.1 - 基本查询

public class QueryTest {

    private static final File INDEX_DIR_FILE = new File("/Users/zhangsiyao1/Desktop/indexDir");

    @Test
    public void baseSearchTest() throws IOException, ParseException {
        /* 索引目录对象 */
        Directory directory = FSDirectory.open(INDEX_DIR_FILE);
        /* 索引读取工具 */
        DirectoryReader directoryReader = DirectoryReader.open(directory);
        /* 索引搜索工具 */
        IndexSearcher indexSearcher = new IndexSearcher(directoryReader);

        /*
        * 创建查询解析器
        * 1.查询字段名称
        * 2.分词解析器
        * */
        QueryParser queryParser = new QueryParser("title", new IKAnalyzer());
        /* 获取查询对象 */
        Query query = queryParser.parse("谷歌地图之父拉斯");

        /*
        * 搜索数据
        * 1.查询解析器解析后的查询结果
        * 2.查询结果的最大条数
        * */
        TopDocs topDocs = indexSearcher.search(query, 10);

        /* 获取总条数 */
        int totalHits = topDocs.totalHits;
        System.out.println("本地搜索共查询到 " + totalHits + " 匹配记录");

        /*
        * 得分文档数组
        * 1.doc 文档编号(ID)
        * 2.score 文档得分数
        * */
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;

        for (ScoreDoc scoreDoc : scoreDocs) {
            /* 文档编号 */
            int docID = scoreDoc.doc;
            /* 通过索引读取器 根据文档编号获取文档 */
            Document document = directoryReader.document(docID);

            System.out.println("DocID: " + document.get("id"));
            System.out.println("Title: " + document.get("title"));
            
            /* 文档得分 */
            System.out.println("Score: " + scoreDoc.score);
        }
    }
}

6.查询索引详解

public class LuceneQueryUtils {

    private static final File INDEX_DIR_FILE = new File("/Users/zhangsiyao1/Desktop/indexDir");

    public static void queryByQuery(Query query, int maxResult) throws IOException {
        /* 索引目录对象 */
        Directory directory = FSDirectory.open(INDEX_DIR_FILE);
        /* 索引读取工具 */
        DirectoryReader directoryReader = DirectoryReader.open(directory);
        /* 索引搜索工具 */
        IndexSearcher indexSearcher = new IndexSearcher(directoryReader);
        /* 搜索数据 */
        TopDocs topDocs = indexSearcher.search(query, maxResult);

        int totalHits = topDocs.totalHits;
        System.out.println("本地搜索共查询到 " + totalHits + " 匹配记录");
        System.out.println("=======================================");
        /*
        * 得分文档数组
        * */
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;

        for (ScoreDoc scoreDoc : scoreDocs) {
            /* 文档编号 */
            int docID = scoreDoc.doc;
            /* 通过索引读取器 根据文档编号获取文档 */
            Document document = directoryReader.document(docID);
            System.out.println("DocID: " + document.get("id"));
            System.out.println("Title: " + document.get("title"));
            System.out.println("Score: " + scoreDoc.score);
            System.out.println("=======================================");
        }
    }
}

6.1 - MultiFieldQueryParser

6.2 - Query

6.3 - IndexSearch

6.4 - TopDocs

6.5 - ScoreDoc

7.高级查询

7.1 - 词条查询

public void termQueryTest() throws IOException {
   TermQuery termQuery = new TermQuery(new Term("title", "谷歌地图"));
   LuceneQueryUtils.queryByQuery(termQuery, 10);
}

7.2 - 通配符查询

public void wildcardQuery() throws IOException {
   WildcardQuery query = new WildcardQuery(new Term("title", "*歌"));
   LuceneQueryUtils.queryByQuery(query, 10);
}

7.3 - 模糊查询

public void fuzzyQueryTest() throws IOException {
   FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("title", "facebool"), 1);
   LuceneQueryUtils.queryByQuery(fuzzyQuery, 10);
}

7.4 - 数值范围查询

public void numericRangeQueryTest() throws IOException {
   NumericRangeQuery<Long> numericRangeQuery = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
   LuceneQueryUtils.queryByQuery(numericRangeQuery, 10);
}

7.5 - 组合查询

public void booleanQueryTest() throws IOException {
   NumericRangeQuery<Long> numericRangeQuery1 = NumericRangeQuery.newLongRange("id", 1L, 3L, true, true);
   NumericRangeQuery<Long> numericRangeQuery2 = NumericRangeQuery.newLongRange("id", 2L, 4L, true, true);
   BooleanQuery booleanQuery = new BooleanQuery();
   booleanQuery.add(numericRangeQuery1, BooleanClause.Occur.MUST_NOT);
   booleanQuery.add(numericRangeQuery2, BooleanClause.Occur.SHOULD);
   LuceneQueryUtils.queryByQuery(booleanQuery, 10);
}

8.修改索引

public class UpdateIndexTest {

    private static final File INDEX_DIR_FILE = new File("/Users/zhangsiyao1/Desktop/indexDir");

    /*
    * 1.Lucene 底层先删除所有匹配的索引 再添加新文档
    * 2.一般修改功能会根据 Term 词条进行匹配
    * 3.根据一个唯一不重复字段进行匹配(ID)
    *
    * 问题: update 时 Term 词条搜索 要求 ID 必须是字符串 如果不是则不能使用这个方法
    * 解决: 先删除该词条 再添加更新后的词条
    * */
    @Test
    public void updateTest() throws IOException {
        /* 创建目录对象 */
        FSDirectory directory = FSDirectory.open(INDEX_DIR_FILE);
        /* 创建索引写出器配置对象 */
        IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
        /* 创建索引写出器 */
        IndexWriter writer = new IndexWriter(directory, config);

        Document document = new Document();
        document.add(new StringField("id", "1", Field.Store.YES));
        document.add(new TextField("title", "谷歌地图之父跳槽facebook为了加入Amazon", Field.Store.YES));
        writer.updateDocument(new Term("id", "1"), document);

        writer.commit();
        writer.close();
    }
}

9.删除索引

public class UpdateIndexTest {

    /*
    * 删除索引
    * */
    @Test
    public void deleteTest() throws IOException {
        /* 创建目录对象 */
        FSDirectory directory = FSDirectory.open(INDEX_DIR_FILE);
        /* 创建索引写出器配置对象 */
        IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
        /* 创建索引写出器 */
        IndexWriter writer = new IndexWriter(directory, config);

        /*
        * 1.根据词条 Term 进行删除 只能匹配 字符串类型 字段
        * */
        writer.deleteDocuments(new Term("id", "1"));

        /*
        * 2.根据 Query 删除 可以匹配任何类型的字段
        * */
        NumericRangeQuery<Long> numericRangeQuery = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
        writer.deleteDocuments(numericRangeQuery);

        /* 3.删除所有 */
        writer.deleteAll();

        writer.commit();
        writer.close();
    }
}

10.Lucene 高级使用

10.1 - 高亮显示

  1. SimpleHTMLFormatter:HTML 格式化工具
  2. Highlighter:高亮工具
@Test
public void highLightTest() throws IOException, ParseException, InvalidTokenOffsetsException {
    /* 目录对象 */
    FSDirectory directory = FSDirectory.open(INDEX_DIR_FILE);
    /* 读取工具 */
    DirectoryReader reader = DirectoryReader.open(directory);
    /* 搜索工具 */
    IndexSearcher searcher = new IndexSearcher(reader);

    /* parse 方式获得 Query 对象 */
    QueryParser queryParser = new QueryParser("title", new IKAnalyzer());
    Query query = queryParser.parse("谷歌地图");

    /* HTML 格式化器 */
    Formatter formatter = new SimpleHTMLFormatter("<em>", "</em>");
    QueryScorer queryScorer = new QueryScorer(query);
    /* 准备高亮工具 */
    Highlighter highlighter = new Highlighter(formatter, queryScorer);

    /* 搜索 */
    TopDocs topDocs = searcher.search(query, 10);
    System.out.println("TotalHits: " + topDocs.totalHits);

    for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
        Document document = reader.document(scoreDoc.doc);
        /*
        * 高亮工具处理普通查询结果
        * 参数一: 分词器
        * 参数二: 高亮字段名
        * 参数三: 高亮字段原始值
        * */
        String highLightTitle = highlighter.getBestFragment(new IKAnalyzer(), "title", document.get("title"));
        System.out.println(highLightTitle);
    }
}
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-highlighter</artifactId>
    <version>${lucene.version}</version>
</dependency>

10.2 - 排序

Sort sortArray = new Sort(new SortField("id", SortField.Type.LONG, true));
TopDocs topDocs = searcher.search(query, 10, sortArray);

悄悄话 🌈


彩蛋 🐣


如果你觉得我的分享对你有帮助的话,请在下面👇随手点个喜欢 💖,你的肯定才是我最大的动力,感谢。

上一篇 下一篇

猜你喜欢

热点阅读