`
wbj0110
  • 浏览: 1612511 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

lucene4.5源码分析系列:搜索过程

阅读更多

IndexSearcher是搜索的入口,主要提供的api都是关于search的。关于搜索,比较有意思的话题有这么几个:如何计算打分,这个问题已经在空间向量模型一文中讨论过?如何从一个搜索词得到一个Query对象?如何从Query对象到评分器从而计算打分的?几个重要的参数是如何在被组织起来计算的,比如n, filter, sort, collector等。另外,分页是如何进行的?

  本文以展示搜索的组织和整个搜索过程为主,其他未讨论的问题将会在接下来陆续讨论。

  大致上,前两个search属于简单搜索一类的,接下来两个api是带Collector的,最后三个api是带排序的

 

[java] view plaincopy
 
  1. public TopDocs search(Query query, int n)  throws IOException;  
  2. public TopDocs search(Query query, Filter filter, int n) throws IOException;  
  3. public void search(Query query, Filter filter, Collector results) throws IOException;  
  4. public void search(Query query, Collector results) throws IOException;  
  5. public TopFieldDocs search(Query query, Filter filter, int n,  
  6.                              Sort sort) throws IOException;  
  7. public TopFieldDocs search(Query query, Filter filter, int n,  
  8.                              Sort sort, boolean doDocScores, boolean doMaxScore) throws IOException;  
  9. public TopFieldDocs search(Query query, int n,  
  10.                              Sort sort) throws IOException;  

  以及一堆用于分页或深度查询用的searchAfter。

 

[java] view plaincopy
 
  1. public TopDocs searchAfter(ScoreDoc after, Query query, int n) throws IOException;  
  2. public TopDocs searchAfter(ScoreDoc after, Query query, Filter filter, int n) throws IOException;  
  3. public TopDocs searchAfter(ScoreDoc after, Query query, Filter filter, int n, Sort sort) throws IOException;  
  4. public TopDocs searchAfter(ScoreDoc after, Query query, int n, Sort sort) throws IOException;  
  5. public TopDocs searchAfter(ScoreDoc after, Query query, Filter filter, int n, Sort sort,  
  6.                              boolean doDocScores, boolean doMaxScore) throws IOException;  

关键字的组织

  讲到搜索,首先要从理解输入的关键字讲起,也就是QueryParser如何理解输入关键字(当然,也可以自己手动的构造不同的Query),然后如何组织它们。关于关键字的组织,应该要想到如何表达与或非这样的谓词逻辑以便搜索的完整。其次,不同的关键字有不同的种类用以应对不同的场合,比如,模糊匹配,顺序匹配,正则表达式匹配,数字范围匹配等等,熟悉面向对象的同学应当想到将各个种类的匹配方式封装到类里,然后再用谓词逻辑连接起来,从而构成一个完整的查询。没错,lucene也是这样做的。由于构建谓词逻辑的类与构建其他关键字的类继承了相同的接口,操作谓词类就等于操作整个查询链,所以这里是典型的装饰器模式,它的好处是用一个类表示了一整个结构,并且遵循统一的接口形成一个规范。如图,多个关键字搜索的情况下,一个BooleanQuery便可以表示整棵树。事实上,后面的Weight和Scorer也是这样的结构。

  下表简单的介绍了一些常见的Query。关于各种不同的Query如何融入进这样一个统一的框架中来,有许多值得讲的地方,这里主要介绍从Query产生后真正查询的过程,暂且略讲Query的产生部分以及各个Query的特点。

  整个搜索的横向上主要是以3种类来组织的,即Query,Weight, Scorer。Query负责组织查询对象,Weight负责计算查询对象的权重,Scorer负责计算打分;纵向上依靠BooleanQuery组织成一整颗树结构,其非叶子节点就是BooleanQuery,叶子节点是其他Query,形成Query后,Weight对象的组织就依靠Query树递归一步一步构建起来的,Scorer也是类似的。

搜索流程

  明白了上面这个纲之后再来看search这个过程,就比较容易理解了,大致步骤如下:

  1. 通过createNormalizedWeight从Query创建Weight,Weight是一个非常重要的对象,通过它来计算查询评分的因子---权重。

  2. 通过TopFieldCollector.create生成Collector,Collector的主要作用是用来搜集原始的评分结果,在结果的基础上可以进行排序,过滤等操作。

  3. 从weight中生成Scorer,Scorer的目的是用于计算评分并生成结果

  4. 调用Scorer的score方法计算评分结果并用collector搜集文档结果集

  5. 从collector的结果中得到topDocs

  以下是这个过程一个大致的顺序图。

  接下来我们一步一步来看每个步骤。

  在实际创建Weight之前,可以指定Filter过滤不想搜索的内容,我们可以了解下lucene是如何实现这个filter的。

  lucene中通过一个FilteredQuery包装原来的Query来完成这件事情,这里的Filte好像一道门禁,使得搜索能够从索引中获得能够通过门禁的文档ID,下面的MultiTermScorer重写ConstantScorerQuery时也是使用了Filter, Filter中有个重要的方法getDocIdSet,这个方法过滤对应的文档,然后将结果集返回。在这里可以根据需要选择不同的filter,或者自己定义filter来满足各种过滤或者安全需求。

 

[java] view plaincopy
 
  1. public abstract DocIdSet getDocIdSet(AtomicReaderContext context, Bits acceptDocs) throws IOException;  

 

  在所有步骤中,首先是创建Weight并计算部分评分,源码如下:

 

[java] view plaincopy
 
  1. public Weight createNormalizedWeight(Query query) throws IOException {  
  2.   query = rewrite(query);  
  3.   Weight weight = query.createWeight(this);  
  4.   float v = weight.getValueForNormalization();  
  5.   float norm = getSimilarity().queryNorm(v);  
  6.   if (Float.isInfinite(norm) || Float.isNaN(norm)) {  
  7.     norm = 1.0f;  
  8.   }  
  9.   weight.normalize(norm, 1.0f);  
  10.   return weight;  
  11. }  

 

  创建weight的过程分为这样几步

  1. 重写Query树。重写的主要目的是将整棵树上一些需要改变搜索关键词的地方重新改变。比如,整个索引建立时有这样几个term,"算法","算术",在搜索"算*"时QueryParser将其解释为PrefixQuery,在重写这步便会搜索所有前缀为"算"的term,并用ConstantScoreQuery替换掉原来的PrefixQuery,在ConstantScorer中会将"算*"替换为"算法", "算术"两个实际的term,进而转化成求解一般term评分,这是典型的将复杂问题转换成已知问题求解的思想。

  2. 根据Query树创建Weight树,这个创建过程是一个递归的过程。调用顶层query.createWeight,就会将整棵Weight树构建起来。

  3. 计算ValueForNormalization

  4. 根据ValueForNormalization计算queryNorm

  5. 计算公共部分打分公式(3,4,5参见打分公式一文),之所以这里会计算一部分打分公式,因为这部分是每个文档计算时共用的。

  其次通过TopScoreDocCollector.create创建Collector查询文档数n会被传入到collector,并且在每次新加入一个文档时验证是否已经达到上限n。

  接下来从Weight中生成Scorer,这部分其实类似从Query创建Weight。值得一提的地方有3个,BooleanScorer2, MultiTermScorer, TermScorer。

  TermWeight中是如何得到TermScorer的呢?getTermsEnum会得到TermsEnum,再由termsEnum得到DocsEnum,这两个都是比较重要的对象,DocsEnum中的nextDoc可以遍历命中的文档

 

[java] view plaincopy
 
  1. public Scorer scorer(AtomicReaderContext context, boolean scoreDocsInOrder,  
  2.       boolean topScorer, Bits acceptDocs) throws IOException {  
  3.     assert termStates.topReaderContext == ReaderUtil.getTopLevelContext(context) : "The top-reader used to create Weight (" + termStates.topReaderContext + ") is not the same as the current reader's top-reader (" + ReaderUtil.getTopLevelContext(context);  
  4.     final TermsEnum termsEnum = getTermsEnum(context);  
  5.     if (termsEnum == null) {  
  6.       return null;  
  7.     }  
  8.     DocsEnum docs = termsEnum.docs(acceptDocs, null);  
  9.     assert docs != null;  
  10.     return new TermScorer(this, docs, similarity.simScorer(stats, context));  
  11.   }  

 

  在BooleanScorer2中,将Scorer的组织分成了3类,即requiredScorers,optionalScorers, prohibitedScorers,其实就分别代表了与或非。

  在MultiTermScorer及其子类中,之前的rewrite方法会将query重新封装,可以看到,比较重要的是,用了一个ConstantScoreQuery,对应的Weight是ConstantWeight,对应的Scorer是ConstantScorer 

 

[java] view plaincopy
 
  1. Query result = new ConstantScoreQuery(new MultiTermQueryWrapperFilter<MultiTermQuery>(query));  

  随后调用scorer中的score方法, 该方法遍历所有结果文档,并将目标文档保存在一个优先队列中,该优先队列负责排序。这个过程见下面的代码

[java] view plaincopy
 
  1. public void score(Collector collector) throws IOException {  
  2.   assert docID() == -1// not started  
  3.   collector.setScorer(this);  
  4.   int doc;  
  5.   while ((doc = nextDoc()) != NO_MORE_DOCS) {  
  6.     collector.collect(doc);  
  7.   }  
  8. }  

  该优先队列是一个比较重要的结构,之所以说它重要,因为一方面,查询文档数会作为优先队列的大小;另一方面,排序的也是通过优先队列完成的。

  关于比较的流程,可以看到,调用collect方法之后,紧接着就是调用优先队列的updateTop及downHeap,downHeap就是真正调整队列位置的地方,但是,它的判断依据是lessThan来的,而lessThan正是利用类似Comparator的比较器来灵活的实现优先度的排序。

  

  我们来看一个MultiComparatorsFieldValueHitQueue中的lessThan方法,如果有多个field需要比较,它会按照field的顺序循环,分别比较这堆field,一旦判断两者分数不一样就返回比较结果,否则,就要按照顺序找下一个field比较。

 

[java] view plaincopy
 
  1. protected boolean lessThan(final Entry hitA, final Entry hitB) {  
  2.   
  3.  assert hitA != hitB;  
  4.  assert hitA.slot != hitB.slot;  
  5.   
  6.  int numComparators = comparators.length;  
  7.  for (int i = 0; i < numComparators; ++i) {  
  8.    final int c = reverseMul[i] * comparators[i].compare(hitA.slot, hitB.slot);  
  9.    if (c != 0) {  
  10.      // Short circuit  
  11.      return c > 0;  
  12.    }  
  13.  }  
  14.  // avoid random sort order that could lead to duplicates (bug #31241):  
  15.  return hitA.doc > hitB.doc;  

  优先队列实现并不难,但是它灵活的实现了许多不同field的比较,因而很值得我们借鉴。

  比较特殊的地方是searchAfter,他用到了PagingFieldCollector,因此它在插入优先队列之前还会先过滤掉afterDoc文档之前的所有文档,从而达到分页的效果。

 

  scorer最后会调用到similarity中的设置来进行实际的打分,similarity实现了一个简单的策略模式,通过不同的策略选取,可以实现不同的评分算法。

  最后从collector中得到TopDocs,这一步仅仅是将之前的搜索结果整理成TopDocs的形式。

 评分,排序,分页,过滤的顺序

  好了,我们来整理一下一个评分,排序,分页和过滤的过程:
  1. 首先会从QueryParser得到一颗Query对象树。
  2. 接下来计算打分公式中的公共部分,同时得到了weight对象树
  3. 过滤可用的文档,得到scorer
  4. 调用scorer的score方法开始真正的评分
  5. 在需要分页的地方进行过滤,最后做排序
 
              优先队列三大利器——二项堆、斐波那契堆、Pairing 堆 http://dsqiu.iteye.com/blog/1714961

http://blog.csdn.net/liweisnake/article/details/11528711

分享到:
评论

相关推荐

    lucene3源码分析

    ### Lucene3源码分析知识点概述 #### 一、全文检索的基本原理 ##### 1. 总论 全文检索系统是一种高效的信息检索技术,能够帮助用户在海量文档中快速找到包含特定关键词的信息。Lucene是Java领域内最受欢迎的全文...

    IK分词器集成lucene4.5使用方法

    2. **配置Analyzer**:在Lucene的索引创建和搜索过程中,需要定义Analyzer。对于IKAnalyzer,可以这样配置: ```java import org.apache.lucene.analysis.cn.ik.IKAnalyzer; Analyzer analyzer = new IKAnalyzer();...

    Lucene 3.0 原理与代码分析PDF

    Lucene学习总结之四:Lucene索引过程分析(1) Lucene学习总结之四:Lucene索引过程分析(2) Lucene学习总结之四:Lucene索引过程分析(3) Lucene学习总结之四:Lucene索引过程分析(4) www.chinaandroid.com

    SSI集成lucene4.5使用案例

    在本文中,我们将深入探讨如何将Lucene 4.5集成到SSI(Server Side Includes)系统中,以实现高效、灵活的全文搜索引擎功能。Lucene是Apache软件基金会的一个开源项目,它是一个高性能、全功能的文本搜索库,而SSI则...

    Lucene学习源码.rar

    通过学习Lucene源码,我们可以定制自己的分词器、查询解析器,甚至优化搜索算法,以满足特定的搜索需求。例如,在中文环境下,可以使用IK Analyzer或者jieba分词库来增强对中文的支持。 总结,Lucene作为Java平台上...

    lucene-core-7.7.0-API文档-中文版.zip

    赠送jar包:lucene-core-7.7.0.jar; 赠送原API文档:lucene-core-7.7.0-javadoc.jar; 赠送源代码:lucene-core-7.7.0-sources.jar; 赠送Maven依赖信息文件:lucene-core-7.7.0.pom; 包含翻译后的API文档:lucene...

    Lucene 2.4.1源码分析

    通过分析StandardAnalyzer的源码,我们可以了解到Lucene如何将复杂的文本预处理工作封装在Analyzer中,使得开发者可以专注于构建搜索应用,而无需关心底层的分词和过滤细节。StandardAnalyzer虽然简单易用,但也可以...

    Lucene3.5源码jar包

    10. **性能调优**:通过分析源码,开发者可以了解到如何调整各种参数,如缓存大小、合并策略等,来优化Lucene的性能。 总的来说,深入学习Lucene 3.5.0的源码,可以帮助开发者掌握全文检索的核心技术,了解其内部...

    24 Lucene学习总结之八:Lucene的查询语法,JavaCC及QueryParser(1).doc

    24 Lucene学习总结之八:Lucene的查询语法,JavaCC及QueryParser(1)

    lucene5.0源码包

    - **分析器(Analyzer)**:分析器负责将用户输入的文本进行分词,形成可以被索引的术语。5.0版本提供了更多可定制的分析器,如语言敏感分析器,满足不同场景的需求。 - **查询解析器(Query Parser)**:将用户的...

    Lucene.Net源码与说明文档

    通过阅读源码,开发者可以深入了解搜索引擎的工作原理,如倒排索引的构建、查询的执行过程以及结果的排序算法。 1. **索引模块**:这是 Lucene.Net 的核心部分,负责将文本数据转换为可搜索的索引。主要包括文档...

    lucene-sandbox-6.6.0-API文档-中文版.zip

    赠送jar包:lucene-sandbox-6.6.0.jar; 赠送原API文档:lucene-sandbox-6.6.0-javadoc.jar; 赠送源代码:lucene-sandbox-6.6.0-sources.jar; 赠送Maven依赖信息文件:lucene-sandbox-6.6.0.pom; 包含翻译后的API...

    lucene源码分析1111

    《深入剖析Lucene源码》 Lucene是一个高性能、全文本搜索库,广泛应用于各种搜索引擎的开发中。...对于那些想要深入研究全文检索技术或者构建自己的搜索引擎的开发者来说,Lucene的源码分析无疑是宝贵的参考资料。

    lucene.net 2.9.1 源码

    《深入剖析Lucene.NET 2.9.1:源码解析与应用开发》 Lucene.NET 2.9.1是开源搜索引擎库Lucene的.NET版本,它为.NET开发者提供了强大的全文检索和索引功能。这个版本的源码提供了一个宝贵的资源,帮助我们理解其内部...

    solr-4.5源码包

    Solr是Apache软件基金会的一个开源项目,它是一个全文搜索引擎服务器,提供了高级的搜索功能和管理API。Solr-4.5.0是该系统的一个重要版本,它在之前的版本基础上进行了许多改进和优化,旨在提高搜索性能和稳定性。...

    lucene 2.4.1源码在eclipse调试运行通过

    2.4.1版本是Lucene的一个重要里程碑,本文将详细讲解如何在Eclipse集成开发环境中对Lucene 2.4.1的源码进行调试和运行,以帮助开发者更好地理解和利用这一强大的搜索引擎工具。 首先,我们需要准备的是Eclipse IDE...

    wangwangla#qiuzhao#第三章 Lucene的源码分析1

    反向信息词---&gt;文档文件有:数据类型:Lucene索引文件中Byte:最基本的数据类型Uint32:由4个byte组成VInt:长度变化的数据类型,可能包含多

    lucene5 源码教程

    本教程将深入探讨Lucene 5版本的源码,特别关注拼音检索和分词器的应用,帮助开发者更好地理解和利用这一强大的搜索技术。 一、Lucene概述 Lucene是一个高性能、全文本搜索库,它提供了完整的搜索功能,包括索引、...

    lucene源码和程序

    4. **分析器(Analyzer)**:分析器负责处理文档中的文本,将其转化为一系列可供搜索的词项(Term)。Lucene提供了多种内置分析器,如标准分析器(StandardAnalyzer),也可以自定义分析器以适应特定需求。 5. **...

    lucene实战源码.rar

    《Lucene实战源码》是一本深入探讨Apache Lucene搜索引擎库的书籍,其配套的光盘代码包含了书中各个章节的示例和实验项目。Lucene是Java开发的全文搜索引擎库,广泛应用于各种信息检索和文本分析场景。通过研究这...

Global site tag (gtag.js) - Google Analytics