本篇文章将介绍Lucene中用于存储文档号的数据结构DocIdSet,该抽象类的具体实现有以下几种,我们将一一介绍。
图1:
图1中,DocsWithFieldSet跟RoaringDocIdSet已经介绍过了。建议先看下文章RoaringDocIdSet,因为该实现中会使用到图1中的其他DocIdSet对象,有助于理解。
无论在索引阶段还是搜索阶段,都会用到DocIdSet来存储文档号。例如在索引阶段,使用DocsWithFieldSet收集正排信息对应的文档号、使用RoaringDocIdSet存储查询缓存;在查询阶段,使用IntArrayDocIdSet或者BitDocIdSet来存储满足数值范围查询(PointRangeQuery)条件的文档号等等。
DocIdSet不仅定义了存储文档号集合使用的数据结构,还定义了如何读取文档号集合的功能,即迭代器。
迭代器
这里说的迭代器即源码中的DocIdSetIterator类,该类定义了四个抽象方法描述了迭代器所能提供的功能。
第一个方法:获取下一个文档号
图2:
当我们使用迭代器遍历文档号集合时,调用该方法会返回一个合法的文档号,如果返回的文档号的值为Integer.MAX_VALUE,说明迭代器中合法的文档号已经读取结束。
上文中的"合法"是什么意思
"下一个"的含义在不同的子类有不同的定义方式,如果某个子类根据自身规则能返回一个文档号,说明这个文档号是合法的。
第二个方法:获取某个文档号的下一个文档号
图3:
文档号集合中的文档号是有前后关系的,该方法通过指定一个参数target,该参数指定了一个文档号,返回这个文档号的下一个文档号。同样的,文档号之间的前后关系也是因不同的子类实现而异。
第三个方法:获取当前的文档号
图4:
在调用了第一个跟第二个方法后会获得一个文档号,那么通过调用第三个方法可以获得这个文档号。
第四个方法:获取迭代器的开销
图5:
正如这个方法注释描述那样,一般情况下(即一般的子类实现)迭代器的开销描述的是迭代器中的文档号集合中的文档数量,但还是取决于不同的子类如何定义开销的计算方式。
DocIdSet的子类实现
接着我们开始介绍图1中的各个子类,介绍每一个子类使用哪种数据结构存储文档号集合以及如何实现迭代器的功能。
IntArrayDocIdSet
从这个子类的命名方式就可以大致了解存储文档号集合的数据结构的类型了,即Int类型数组:
图6:
由于目前IntArrayDocIdSet只有在DocIdSetBuilder(根据文档号集合的疏密程度,构建不同DocIdSet对象的类,在写完这篇文章后会介绍它)中使用,所以IntArrayDocIdSet的设计不具有通用性,这也是为什么构造函数没有用public修饰(图6代码第33行)。我们以一个例子来介绍在源码中该类是如何被使用的。
图7:
在DocIdSetBuilder中收集了一个文档号集合后,首先根据文档号从小到大排序,最终该集合有两种类型:有序单值、有序多值。
- 有序单值:docs数组中不存在重复的文档号。
- 有序多值:docs数组中存在重复的文档号。例如在TermRangeQuery中,会使用DocIdSetBuilder收集每个term对应的文档号,那么会出现这种情况。
为了实现docs数组的复用,通过length指定一个上界,描述docs数组中合法的文档号。
剩余内容看这里:https://www.amazingkoala.com.cn/Lucene/gongjulei/2021/0623/194.html