Lucene经典搜索引擎知识总结

概述

高性能、可扩展的信息检索工具类库

简单强大的核心API

ES的底层工具

扩展性体现在多种扩展包,常见如spellchecker拼写检查模块,highlighter 搜索结果高亮模块。

只要数据源可以转换为文本形式,lucene 即可创建索引。(如pdf,xml,txt等)

组件

索引组件

Acquire Content 获取内容组件

将需要被查询搜索的数据加载到搜索程序。

该模块一般是动态的,动态是指增量数据处理。

Build Document 构建文档

原始内容转换为Lucene内部使用的结构。

该结构被称为 Document 文档。

主要包括几个带值的域,如标题,正文,摘要,作者,链接。

域由程序员控制。将原文处理后分别写入不同的域中。

Analyze Document 分析文档

核心步骤/组件,技术含量最高

搜索引擎不能直接对文本进行索引,必须将文本分割成一系列的 语汇单元。

Index Document 文档索引

单词挂到哪个文档上,建立索引

搜索组件

用户查询接口

搜索界面,web,app等搜索框,UI界面。

构建查询

程序将前端请求转换为Lucene 识别的“查询对象(Query)”

Lucene 提供了一个 查询解析器框架(QueryParser),可以将用户的输入转换为查询对象。

执行查询

查询索引并返回查询对象匹配的文档。使用Lucene查询组件。

查询请求这里提供了扩展机制,可自定义搜索,过滤,排序等功能。

核心知识

索引和搜索

索引
IndexWriter 索引写入器

索引过程核心组件,负责创建新索引或打开已有索引。向索引中增删改被索引文档的信息。

IndexWriter 提供对索引文件的写入操作,但不能读取或搜索。

Directory

抽象类。描述Lucene索引存放位置。

子类负责指定具体的索引存储路径。

Document 文档对象

代表域(Field)的集合。

Lucene 对 Document 对象进行存储和索引,会对Document对象中的 Field,满足被索引条件的,进行索引。

document 设计应谨慎,类似表结构设计。

向索引增加文档

// 1. 使用默认分析器添加文档
indexWriter.addDocument(Document doc)

// 2. 指定的分析器添加文档和语汇单元分析操作。
indexWriter.addDocument(Document doc, Analyzer analyzer)

文档信息删除

删除操作并不会立即执行,而是会存放在缓冲区内,与加入文档类似,最后Lucene会通过周期性刷新文档目录来执行该操作。

可以通过writer.commit() 或者 writer.close() 来立即生效。

writer.forceMerge(1); 执行这个可以让磁盘文件也强制刷新索引磁盘文件。

// 1. 负责删除包含项的所有文档
indexWriter.deleteDocuments(Term term)

// 2. 负责删除包含项数组任一元素的所有文档
indexWriter.deleteDocuments(Term[] terms)

// 3. 负责删除匹配查询语句的所有文档
indexWriter.deleteDocuments(Query query)

// 4. 负责删除匹配查询语句数组任一元素的所有文档
indexWriter.deleteDocuments(Query[] querys)

// 5. 负责删除索引中全部文档
indexWriter.deleteAll()

更新文档

求新文档必须包含旧文档的所有域,包括内容未发生改变的域。因为Lucene只能删除整个过期文档,然后再向索引中添加新文档。

更新其实就是先删除,再增加,所以注意删除的那个条件不要删太多了。

indexWriter.updateDocument(Term term, Document newDoc)
Field

每一个域,都有域名和域值,以及一组选项来控制索引操作行为。

域可以同名,该情况下,搜索时所有域的文本会连接在一起,作为一个文本域处理。

doc.add(new Field(1, 2, 3, 4));
  • 1和2 field 的 key 和 value
  • 3 存储策略
    Field.Store.YES 存储
  • 4 分词策略
    Field.Index.NOT_ANALYZED_NO_NORMS不分词且不存储加权基准
    Field.Index.NOT_ANALYZED 不分词
    Field.Index.ANALYZED 分词
常用Field类
数据类型 是否分词 是否索引 是否存储 说明
StringField 字符串 自定义 不会进行分词,会将整个串当做一个“语汇单元”存储到索引中,比如“身份证号
LongPoint Long型 自定义 用来构建一个Long型Field,进行分词和索引,比如“价格”
StoredField 多种类型 不分析,不索引,但是Field会存储在文档中
TextField 字符串或流 自定义 如果值参数是Reader,Lucene猜测值比较大,会采用Unstored策略
// 使用Reader而非String来表示域值,在内存中保存String代价比较高或者不太方便时,存储的域值比较大的时候使用。
TextField(String name, Reader reader, Store store)

// TokenStream 用来表示一组已分析过的语汇单元,相当于自定义语汇单元
TextField(String name, TokenStream tokenStream)
加权操作

权值高的容易排在前面,更容易被查出来。默认都是1.0。

field.setBoost(float boost)
索引特殊字段
索引数字
  1. 数字内嵌在将要索引的文本中。
  2. 特定域只包含数字,将他们作为数字域值来索引,能在搜索和排序中对它们进行精确匹配。

第一种情况,只需要提供一个不丢弃数字的分析器

例如“WhitespaceAnalyzer” “StandardAnalyzer”

第二种情况,可以使用 IntPoint LongPoint FloatPoint DoublePoint这种特殊的域。

索引日期和时间

将日期和时间转换为等价的数值。

doc.add(new LongPoint("timestamp", new Date().getTime()));
搜索
IndexSearcher 索引查询器

用于搜索IndexWriter类创建的索引。

主要利用Directory实例来定位指定索引。

提供了大量搜索方法。

搜索索引的门户。

所有搜索都通过IndexSearcher进行,他们会调用该类中重载的search方法。

// 第一步:我们需要一个用于索引的目录,大多数情况下我们搜索的索引都是存在于文件系统的
Directory dir = FSDirectory.open(new File("/path/to/index").toPath());

// 第二步:创建一个用于访问dir索引资源的indexReader,这步骤比较消耗资源,不要频繁打开,尽量复用
IndexReader reader = IndexReader.open(dir);

// 第三步:创建用于搜索的门户实例IndexSearcher
IndexSearcher searcher = new IndexSearcher(reader);

// 进行搜索 n 表示匹配到的前多少个结果返回
TopDocs r = searcher.search(Query query, int n)

如果IndexReader需要更新,可以使用如下方式更新

IndexReader newReader = DirectoryReader.openIfChanged(oldReader);
if(reader != newReader) {   
    reader.close();   
    reader = newReader;   
    searcher = new IndexSearcher(reader);
}
Term

搜索功能的基本单元。与TermQuery对象一起使用。

例如:

// 搜索 文档中 字段名为 fieldName 中 包含 Hello  的
Query q = new TermQuery(new Term("fieldName", "Hello"));
// 10 表示前10条
TopDocs hits = searcher.search(q, 10);
Query

查询对象,有很多子类。

封装某种查询类型的具体子类。Query实例将被传递给IndexSearch的search方法

TermQuery、BooleanQuery、RangeQuery、PrefixQuery、PhraseQuery、MultiPhraseQuery、FuzzyQuery、WildcardQuery、
RegexQuery、SpanQuery等。

TermQuery

对索引中特定项进行搜索是最基本的搜索方式。

常用于根据关键字查询文档。

TermRangeQuery

索引中的各个Term对象会按照字典序排列,并允许Lucene的TermRangeQuery对象提供的范围进行搜索。

搜索时可以包含或者不包含 起始项 或 终止项,如果不包含则说明该端为无边界的。

// 包含起点和终点
TermRangeQuery query = new TermRangeQuery("title2", new BytesRef("s"), new BytesRef("e"), true, true);

两个Boolean对象参数表示是否包含搜索范围的起点和终点。

PointRangeQuery

若使用(IntPoint、LongPoint…)作为索引域,则可使用PointRangeQuery在某个特定范围内搜索该域。

// 1. IntPoint.newExactQuery 精确查询
Query query = IntPoint.newExactQuery("age", 18);

// 2. 范围查询,包含边界
query = IntPoint.newRangeQuery("age", 18, 24);
QueryParser

将用户输入的(可读的)查询表达式(类似SQL) 处理成具体的Query对象

// field 默认域名
// analyzer 分析器  常用 new SimpleAnalyzer()
QueryParser parser = new QueryParser(String field, Analyzer analyzer);
Query query = parser.parse("one*");
TopDocs docs = searcher.search(query, 10);

基本查询表达式

表达式中如指定了域,则不搜索默认域。

查询表达式 匹配文档
one 默认域包含one的项文档
one two 默认域包含one和two中一个或者两个的文档
one OR two 默认域包含one和two中一个或者两个的文档
+one +two 默认域中同时包含one和two文档
one AND two 默认域中同时包含one和two文档
title:one title域中包含one项的文档
title:one -subject:two title域中包含one且subject域中不包含two的文档
title:one AND NOT subject:two title域中包含one且subject域中不包含two的文档
(one OR two) AND three 默认域中包含three且包含one和two中的一个域或者两个的文档
title: “hello world” title域包含hello world短语的文档
title: “hello world”~7 title域中hello和world之间距离小于7的文档
one* 包含one开头的项的文档
lastDate:[1/1/22 TO 12/31/22] lastDate域值在2022年1月1日和2022年12月31日之间的文档
TopDocs

由IndexSearcher.search()方法返回的具有较高评分(排序)的结果。

指针容器。一般指向前N个排名的搜索结果,搜索结果即匹配查询条件的文档。

通过这个可以获取真正的文档。

Document doc = indexSearcher.doc(hits.scoreDocs[i]);
ScoreDoc

提供对TopDocs中每条搜索结果的访问接口。

对特定项的搜索

项:由一个字符串类型的域值和对应的域名组成

Term t = new Term("name", "value");
docs = searcher.search(new TermQuery(t), 10);

分段存储

Lucene引入了“索引分段”的技术,即将一个索引文件拆分为多个子索引文件。

每个段都是一个独立的可被搜索的数据集。

段具有不变性,一旦写入磁盘,就不能再发生改变。

索引写入流程
新增

新建一个段来存储新增的数据。

先写到内存缓冲区,当缓冲区满16mb 或者 自动刷盘(1秒) 或者 commit()/close() 都会创建新的段文件。

删除

在索引文件下新增了一个.del的文件,用来专门存储被删除的数据id

被删除的数据在进行段合并时才会真正被移除,否则还是会被查到。

更新

删除和新增的组合,先在.del文件中记录旧数据,再在新段中添加一条更新后的数据。

注意为了提升性能,Lucene并不会每新增一个文档就生成一个新段,而是采用延迟写的策略,数据先写入内存缓冲区,然后根据策略再批量写入硬盘。

段合并策略

大量的段如果一只保留,会出现如下问题

  1. Linux 服务器打开文件数上限
  2. 每个段都得逐一扫描合并,导致性能降低

段合并就是 将多个小段的索引数据读取到内存中,在内存中合并成一个索引段,然后再写入磁盘,再将旧段文件全部删除。

在执行了flush、commit等方法后,Lucene会基于段的合并策略对索引目录中的段集合进行合并操作。

// 索引合并到指定maxSegments个段
indexWriter.forceMerge(int maxSegments)

分析器

分析(Analysis),在Lucene中指的是将域文本转换为最基本的索引表示单元,项(Term)的过程。

分析器主要应用在

  1. 建立索引期间
  2. 使用QueryParser解析查询期间

内置分析器

分析器 说明
WhitespaceAnalyzer 根据空格拆分语汇单元
SimpleAnalyzer 根据非字母字符拆分文本,并将其转换为小写形式
StopAnalyzer 根据非字母字符拆分文本,然后小写,再移除停用词
KeywordAnalyzer 将整个文本作为一个单一语汇单元处理
StandardAnalyzer 基于复杂的语法来生成语汇单元
StopAnalyzer 完成基本的单词拆分和小写化功能之外,还负责移除一些停用词,构造方法自定义

高级搜索

封装工具类进行各种高级搜索功能。

public class SortingDemo {
    private Directory directory;

    public SortingDemo(Directory directory) {
        this.directory = directory;
    }

    public void displayResults(Query query, Sort sort) throws IOException {
        // 获取查询门户实例
        IndexReader reader = DirectoryReader.open(directory);
        IndexSearcher searcher = new IndexSearcher(reader);

        // 注意:sort参数
        // doDocScores :true,表示强制做评分
        TopDocs results = searcher.search(query, 20,sort, true, false);

        // 分值格式化:保存小数点后6位
        DecimalFormat scoreFormatter = new DecimalFormat("0.######");

        for(ScoreDoc sd : results.scoreDocs) {
            int docID = sd.doc;
            float score = sd.score;
            Document doc = searcher.doc(docID);
            // 对 doc 处理
        }
    }
}

按照索引顺序进行排序

demo.displayResults(query, Sort.INDEXORDER);

通过域进行排序

要求被排序的域整个被索引成单个语汇单元(StringField)

demo.displayResults(query, new Sort(new SortField("xxx",
SortField.Type.STRING)));

倒排序

默认Lucene针对域的排序方向使用的是自然排序。

自然排序会对相关性按照降序排列。

基于域排序的按照升序排列。

// SortField最后一个参数 true ,表示反转顺序。
demo.displayResults(allBooks, new Sort(new SortField("xxxdate", SortField.INT, true)));

通过多个域进行排序

// 按照 a 条件的 评分 排序,相同评分的按照 b 条件降序排序。
SortField a = new SortField("a", SortField.Type.STRING);
SortField b = new SortField("b", SortField.Type.INT, true);

Sort s1 = new Sort(a);
Sort s2 = new Sort(b);

example.displayResults(query, s1, SortField.FIELD_SCORE, s2);

你可能感兴趣的:(lucene,全文检索)