源码系列倒排表
原文: https://www.amazingkoala.com.cn/Lucene/Index/2019/0222/36.html
本篇文章介绍如何生成倒排表,通过一个简单的例子来讲解倒排表的底层存储结构。文章中不会给出详细的源码介绍,只有一些必要的对象,感兴趣的朋友可以看我的 GitHub,对构建倒排表的源码给出了详细的注释: https://github.com/luxugang/Lucene-7.5.0/blob/master/solr-7.5.0/lucene/core/src/java/org/apache/lucene/index/DefaultIndexingChain.java ,此类中的该方法是开始构建倒排表的入口。
另外如果某些域使用了词向量(TermVector),它会额外的生成倒排表,虽然写入的逻辑是类似的,但最终的倒排表的数据结构还是有区别,在后面的文章中会详细介绍。
|
|
例子
图 1:
上图说明:
- 代码第 42 行表示倒排表中会存放 文档号、词频、位置、偏移信息
|
|
- 代码第 46 行表示我们为域名为"content"的域值分词后"book"添加一个值为"hi"的 payload 值,而域名为"title"的中的"book"以及其他 term 都没有 payload 信息,
- 通过自定义分词器来实现 payload,当前例子中的分词器代码可以看 PayloadAnalyzer
|
|
三个关键类
构建倒排表的过程中,主要用到下面三个类:
ByteBlockPool 类
此类中用一个二维数组用来存放 term 的倒排表数据。
|
|
IntBlockPool 类
此类中同样用一个二维数组来存储索引信息,由于所有 term 的倒排表数据都紧凑的存放在 ByteBlockPool 对象的 buffers[][]二维数组中,并且一个 term 的倒排表数据并非存储在一块连续的空间中,所以我们需要 IntBlockPool 对象中的一个二维数组来存储映射到 ByteBlockPool 对象的索引。
|
|
ParallelPostingsArray 类
在这个类中我们只要关心下面几个重要的数组。该类的对象用来处理同一种域名的数据,有几种域名就有对应数量的 ParallelPostingsArray 对象,也就是说下面的数组对于不同文档的相同的域是共用的,这点很重要。
下面所有的数组的下标值都是 termID,termID 用来区分不同 term 的唯一标识,它是一个从 0 开始递增的值,每个 term 按照处理的先后顺序获得一个 termID。
int[] termFreqs;
记录每一个 term 在一篇文档中的词频 frequencies。
int[] lastDocIDs;
记录每一个 term 上次出现是在哪一个文档中。
|
|
int[] lastDocCodes;
记录每一个 term 上一次出现是在哪一个文档中。跟 lastDocIDs[]数组不同的是,数组元素是一个组合值,相同的是当 term 在新的文档中出现后,才将它上一次的文档号写入到倒排表中。
|
|
例子:
|
|
图 2:
|
|
图 3:
int[] lastPositions;
记录每一个 term 在当前文档中上一次出现的位置。
|
|
例子
|
|
图 4:
|
|
图 5:
int[] textStarts;
每一个 term 在 ByteBlockPool 对象的 buffers [ ] [ ]二维数组中的起始位置。
int[] intStarts;
数组元素为每一个 term 在 IntBlockPool 对象的 buffers[ ] [ ] 二维数组中的位置。
|
|
在这里还需要重复的时候, 我们在当前文档第一次处理某个 term 时,才会将这个 term 上次出现的文档号跟词频写入到倒排表中, 而这个 term 的位置跟 payload,则是马上写入。
存储空间的分配和扩容
每次处理一个 term,需要考虑分配跟扩容问题。
分配
该 term 第一次处理,那么需要新分配一个连续的固定大小的存储空间,分配规则如下:
|
|
例子
图 6:
扩容
在图 6 中,对于存储 position、payload、offset 信息的分片,如果前 4 个字节都被记录了,那么此时就会遇到哨兵值 16,表示我们需要扩容了。
图 7:
根据分片规则,扩容后获得的新分片大小为 14,所以上图中下标值 1427 的部分为新的分片。
并且旧分片中下标值 1013 这四个字节作为一个索引值,在读取阶段,通过读取该索引值就可以知道下一个分片在 ByteBlockPool 对象的 buffers [ ] [ ]二维数组中的偏移位置。
写入过程
处理文档 0
处理域名“content”
例子中使用的是自定义分词器 PayloadAnalyzer,所以对于域名“content”来说,我们需要处理 “book”、“is”、“book"共三个 term。
处理 “book”
图 8:
ByteBlockPool 对象的 buffers 数组
- 下标值 0~4:文档 0 中第一个"book"的长度以及对应的 ASCII
- 下标值 10:第一个"book"在文档 0 中的位置,即 0,由于有 payload 信息,组合存储后,位置值为(0 « 1 | 1),即 1
- 下标值 11~14:索引值
- 下标值 15~17:payload 的长度以及对应的 ASCII
- 下标值 18~19:文档 0 中第一个"book"在文档 0 中的偏移位置以及 term 的长度
IntBlockPool 对象的 buffers 数组
- 下标值 0:包含"book"的文档号、词频信息在 ByteBlockPool 对象的 buffers 数组写入的起始位置
- 下标值 1:下一次遇到"book"时,它的 position、payload、offset 信息在 ByteBlockPool 对象的 buffers 数组写入的起始位置
lastPositions[]数组
- lastPositions[]数组下标值为"book"的 termId(0)的数组元素更新为 0。
termFreq[]数组
- termFreq[]数组下标值为"book"的 termId(0)的数组元素更新为 2。
注意点
- 由于文档 0 中的所有 term 没有都处理结束,所以我们还不知道"book"在文档 0 中的词频,所以上图中并没有记录 文档号、词频信息(组合存储)。
处理“book”
图 9:
ByteBlockPool 对象的 buffers 数组
- 下标值 20:第二个"book"在文档 0 中的位置,根据图 8 中,上一个"book"的位置是 0,所以差值存储就是 (1 - 0) = 1,带有 payload,位置信息即为 (1 « 1 | 1),即 3,并且 lastPositions[]数组下标值为当前 term 的 termId(0)的数组元素更新为 1。
- 下标值 21~23:payload 的长度以及对应的 ASCII
- 下标值 24~25:第二个"book"在文档 0 中的偏移位置以及 term 的长度
IntBlockPool 对象的 buffers 数组
- 下标值值 1:下一次遇到"book"时,它的 position、payload、offset 信息在 ByteBlockPool 对象的 buffers 数组写入的起始位置**(下文不赘述这个数组的更新)**
lastPositions[]数组
- lastPositions[]数组下标值为"book"的 termId(0)的数组元素更新为 1 (下文不赘述这个数组的更新)
termFreq[]数组
- termFreq[]数组下标值为"book"的 termId(0)的数组元素更新为 2 (下文不赘述这个数组的更新)
处理“is”
图 10:
ByteBlockPool 对象的 buffers 数组
- 下标值 29~31:文档 0 中的"is"的长度以及对应的 ASCII
- 下标值 37:“is"在文档中的位置,即 2,由于没有 payload,位置信息即为 (2 « 1 | 0),即 4
- 下标值 38~39:“is"在文档 0 中的偏移位置以及 term 的长度
处理域名“author”
处理“book”
图 11:
ByteBlockPool 对象的 buffers 数组
- 下标值 42~46:文档 0 中的域名"title 的"book"的长度以及对应的 ASCII
- 下标值 52:“book"在文档中的位置,即 0,由于没有 payload,位置信息即为 (0 « 1 | 0),即 4
- 下标值 53~54:域名"title 的"book"在文档 0 中的偏移位置以及 term 的长度
注意点
由于是文档 0 中的另一个域"title”,所以即使在"content"中我们处理过"book”,在"title"域中属于一个新的 term。即域之间的倒排表是独立的,尽管数据都存放在同一个 ByteBlockPool 对象的 buffers 数组中
处理文档 1
处理域名“content”
处理“book”
图 12:
!
- 原文作者:知识铺
- 原文链接:https://geek.zshipu.com/post/%E4%BA%92%E8%81%94%E7%BD%91/%E6%BA%90%E7%A0%81%E7%B3%BB%E5%88%97%E5%80%92%E6%8E%92%E8%A1%A8/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com