Lucene 源码系列——多个 MUST 的 Query 的倒排求交集
这种 Query 组合的文档号合并的代码是在 ConjunctionDISI 类中实现。本文通过一个例子来介绍文档号合并逻辑,这篇文章中对于每个关键字如何获得包含它的文档号,不作详细描述,大家可以去看我添加了详细注释的 ConjunctionDISI 类,相信能一目了然。GitHub 地址是:https://github.com/luxugang/Lucene-7.5.0/blob/master/solr-7.5.0/lucene/core/src/java/org/apache/lucene/search/ConjunctionDISI.java。
例子
添加 10 篇文档到索引中。如下图:
图 1:
使用 WhiteSpaceAnalyzer 进行分词。
查询条件如下图,MUST 描述了满足查询要求的文档必须包含"a"、"b"、"c"、"e" 四个关键字。
图 2:
文档号合并
将包含各个关键字的文档分别放入到各自的数组中,数组元素是文档号。
包含“a”的文档号
图 3:
包含“b”的文档号
图 4:
包含“c”的文档号
图 5:
包含“e”的文档号
图 6:
由于满足查询要求的文档中必须都包含"a"、"b"、"c"、"e" 四个关键字,所以满足查询要求的文档个数最多是上面几个数组中最小的数组大小。
所以合并的逻辑即遍历数组大小最小的那个,在上面的例子中,即包含"b"的文档号的数组。每次遍历一个数组元素后,再去其他数组中匹配是否也包含这个文档号。遍历其他数组的顺序同样的按照数组元素大小从小到大的顺序,即包含**"e"的文档号 ---> 包含"a"的文档号 ---> 包含"c"的文档号**。
合并过程
从包含"b"的文档号的数组中取出第一个文档号 doc1 的值 1,然后从包含"e"的文档号的数组中取出第一个不小于 doc1 (1)的文档号 doc2 的值,也就是 5。
比较的结果就是 doc1 (1) ≠ doc2 (5),那么没有必要继续跟其他数组作比较了。因为文档号 1 中不包含关键字"e"。
图 7:
接着继续从包含"b"的文档号的数组中取出不小于 doc2 (5)的值(在图 7 的比较过程中,我们已经确定文档号 1~文档号 5 中都不同时包含关键字"b"跟"e",所以下一个比较的文档号并不是直接从包含"b"的文档号的数组中取下一个值,即 2,而是根据包含"e"的文档号的数组中的 doc2(5)的值,从包含"b"的文档号的数组中取出不小于 5 的值,即 9),也就是 9,doc1 更新为 9,然后再包含"e"的文档号的数组中取出不小于 doc1(9),也就是 doc2 的值被更新为 9:
图 8:
比较的结果就是 doc1 (9) = doc2 (9), 那么我们就需要继续跟剩余的其他数组元素进行比较了,从包含"a"的文档号数组中取出不小于 doc1 (9)的 文档号 doc3 的值,也就是 9:
图 9:
这时候由于 doc1 (9) = doc3 (9),所以需要继续跟包含"c"的文档号的数组中的元素进行比较,从包含"c"的文档号的数组中取出不小于 doc1 (9)的文档号 doc4 的值,也就是 9:
图 10:
至此所有的数组都遍历结束,并且文档号 9 都在在所有数组中出现,即文档号 9 满足查询要求。
结语
本文介绍了使用 BooleanQuery 并且所有的 TermQuery 之间是 MUST 关系的文档号合并原理,在后面的文章中会依次介绍 SHOULD、MUST、MUST_NOT、FILTER 的 TermQuery 的文档号合并原理。
原文链接: https://www.amazingkoala.com.cn/Lucene/Search/2018/1218/27.html