本文承接Collector(三),继续介绍其他的收集器。
图1是Lucene常用的几个Collector:
图1:
根据过滤(filtering)规则,TopFieldCollector派生出的两个子类:
SimpleFieldCollector的collect(int doc)方法的流程图:
图2:
点击查看大图
在介绍每个流程之前,先介绍下TopFieldCollector中的几个变量:
如果业务中不需要用到文档的打分值或者maxScore,强烈建议另这两个参数为false,因为找出maxScore或者文档的打分值需要遍历所有满足查询条件的文档,无法提前结束Collector工作(canEarlyTerminate),在满足查询提交的文档数量较大的情况下,提前结束Collector的收集工作能显著提高查询性能。canEarlyTerminate会在下文中介绍
图3:
如果参数trackMaxScore为true,那么Collector每处理一篇文档,就要记录该文档的打分值score,如果score大于当前maxScore的值,则更新maxScore的值。
图4:
使用优先级队列PriorityQueue来存放满足搜索条件的文档信息(文档信息至少包含了文档打分score以及文档号docId),分数最低的文档信息位于堆顶,堆的大小默认为段中的文档总数(用户也可以指定堆的大小,即用户期望的返回结果TopN的N值)。
如果堆没有满,那么将文档号交给FieldComparator,FieldComparator的概念在FieldComparator的文章中介绍了,不赘述,它用来描述文档间的排序关系(从代码层面讲,通过FieldComparator实现了优先级队列PriorityQueue的lessThan()方法),接着添加文档信息到堆中。
图5:
在添加文档信息到堆中
流程后,如果此时堆正好满了,那么我们需要设置bottom的值,即从我们已经处理的文档中找出最差的(the weakest,sorted last),使得当处理下一篇文档时,只需要跟这个最差的文档进行比较即可。
图6:
在堆满的情况的下,并且collectedAllCompetitiveHits为true,直接可以退出,尽管直接退出了,还是统计了totalHits的值,所以从collectedAllCompetitiveHits的命名方式也可以看出来只是统计了totalHits。
满足下面条件的情况下,collectedAllCompetitiveHits会为true:
canEarlyStopComparing == true && canEarlyTerminate == false
xxxxxxxxxx
trackTotalHits == false && trackMaxScore == false && canEarlyStopComparing
如果满足上面的条件,Lucene会通过抛出异常的方式结束Collector,该异常会被IndexSearcher捕获。这样的好处在于能提高查询性能。比如说某一次查询,我们需要返回Top5,但是满足搜索条件的文档数量有10000W条,那么在Collector中当处理了5篇文档后(文档在段中是有序的),就可以直接返回结果了。
如果条件不满足,即canEarlyTerminate的值为false,那么尽管我们已经收集了Top5的数据(查询结果不会再变化),但是要继续遍历处理剩余的9995篇文档,因为我们需要记录totalHits(如果trackTotalHits为true)或者需要获得打分值最大的文档(如果trackMaxScore为true),所以此时collectedAllCompetitiveHits为true,继续处理下一篇文档
图7:
通过与域比较器(FieldComparator)的bottom值比较,如果比该值更好(competitive),那么先替换bottom,然后重新算出新的bottom,随后还要替换堆顶元素,然后调整堆,算出新的堆顶元素,最后退出继续处理下一篇文档。
图8:
由于通过域比较后,当前文档比bottom还要差,那么先通过canEarlyStopComparing判断出能不能提前结束比较,如果canEarlyStopComparing为false,则退出并处理下一篇文档。
canEarlyStopComparing为false说明段中的文档没有按照搜索期间的排序规则进行排序,所以当前已经收集的TopN未必是最终的搜索结果,所以退出处理下一篇文档。
图9:
可以提前结束域比较,即canEarlyStopComparing为true,并且不可以提前结束Collector的收集工作,即canEarlyTerminate为false,那么同时满足这两个条件就可以设置collectedAllCompetitiveHits为true了。使得处理下一篇文档时就可以走图6中的流程了。
图10:
可以提前结束Collector的收集工作,那么我们先估算剩余满足查询条件的文档数量,通过线性估算出实现,估算方法不展开介绍,没有实际意义。
接着设置一个earlyTerminated的值为true,用户在得到查询结果后可以通过该值来了解Collector提前结束收集工作这个事件。
通过抛出CollectionTerminatedException异常的方式来实现,大家可以点击链接看下源码中对这个异常的解释。
PagingFieldCollector同Collector(二)中的PagingTopScoreDocCollector一样,相对于SimpleFieldCollector实现了分页功能,分页功能的介绍见Collector(二),不赘述,collect(int doc)的流程图是相似的,并且用红圈标记出不同处。
PagingFieldCollector的collect(int doc)方法的流程图:
图11:
点击查看大图
是否已经被收集了
描述的是该文档号是否已经在前面的搜索中被收集了,判断的条件如下,如果为true,说明该文档已经被收集了:
xxxxxxxxxx
topCmp > 0 || (topCmp == 0 && doc <= afterDoc
在下一篇文档中,我们将继续介绍最后一个Collector,即DiversifiedTopDocsCollector,这将是Collector系列文章的最后一篇。
点击下载附件