上一篇文章中,我们介绍了在索引(index)阶段,Lucene 收集了跟点数据相关的信息,这些信息在 flush 阶段会被读取,用于生成索引文件。dim&&.dii,从本文开始介绍索引文件。dim&&.dii 生成的详细过程,如图 1 所示,另外阅读本文中需要前置知识: 索引文件之 dim&&dii

图 1:

MutablePointValues

  MutablePointValues 为图 1 流程图的准备数据,该对象中包含了在索引阶段收集的点数据的信息,我们在文章 索引文件的生成(八)之 dim&&dii 中已经详细的展开介绍了,故这里只简单的列出这些信息:

  • numPoints
  • docIDs
  • numDocs
  • bytes

  MutablePointValues 对象中至少包含了上述的几个信息,但如果 IndexWriter 对象使用了 IndexSort 配置,那么 MutablePointValues 中还要额外包含一个信息:DocMap 对象。

  我们简单的回顾下 IndexSort 这个概念,文章 构造 IndexWriter 对象(一) 中我们说到,在构造一个 IndexWriter 对象的的过程中,其中一个流程是 设置IndexWriter的配置信息IndexWriterConfig,当设置了 IndexSort(IndexSort 的一些介绍见文章 文档提交之 flush(三))配置后,段中的文档会按照 IndexSort 的排序规则进行段内的文档排序,由于每添加(例如 IndexWriter.addDocument(…)方法)一篇文档,就排一次序,在实现(implementation)上不可能执行真正的数据排序(数据之间的交换),故通过一个映射关系,即 DocMap 对象来描述文档之间的排序关系,所以在一个段内,当设置了 IndexSort 配置后,每一篇文档有一个原始的段内文档号,该文档号按照文档被添加的先后顺序,是一个从 0 开始的递增值,而 DocMap 对象中提供了方法来描述文档之间基于 IndexSort 的排序关系,该方法在源码中的注释见 https://github.com/LuXugang/Lucene-7.5.0/blob/master/solr-8.4.0/lucene/core/src/java/org/apache/lucene/index/Sorter.java 中内部类 DocMap 提供的两个方法,这里简单的给出:

1
2
3
4
5
/* Given a doc ID from the original index, return its ordinal in the sorted index. */
    abstract int oldToNew(int docID);

/* Given the ordinal of a doc ID, return its doc ID in the original index. */
    abstract int newToOld(int docID);

执行处理前的初始化的工作

  在当前流程点,我们就可以基于 MutablePointValues 中的信息来 执行处理前的初始化的工作,工作内容为初始化以下几个信息:

  • numLeaves
  • splitPackedValues
  • leafBlockFPs
  • docsSeen
  • parentSplits
  • maxPackedValue
  • minPackedValue

numLeaves

  该值为 long 类型的变量,它用来描述我们随后即将构建的 BKD 树中的叶子节点的数量。

为什么能预先计算出 BKD 树中的叶子节点数量

  在图 1 的流程点 构建BKD树的节点值(node value) 中我们将会介绍 Lucene 将一个节点生成左右子树的划分规则(可以先看下文章 Bkd-Tree 简单的了解划分规则),该规则会使得总是生成一颗 满二叉树,那么根据满二叉树的性质,我们只需要知道点数据的数量就可以计算出 BKD 树中的叶子节点的数量,而点数据的数量在收集阶段实现了统计,并且用 numDocs(见上文中的 MutablePointValues)来描述。

为什么要先计算出 BKD 树中的叶子节点数量

  在后面的流程中,我们可以根据当前处理的节点编号来判断当前节点是内部节点(inner node)还是叶子节点(leaf node),在介绍 构建BKD树的节点值(node value) 时会详细展开介绍 numLeaves 的作用,这里先介绍下节点编号是什么:

图 2:

  图 2 中,按照广度遍历的顺序,依次为每一个节点赋予一个节点编号,节点编号的作用在后面的流程中会使用到。

  另外 numLeaves 也用来初始化例如 splitPackedValues、leafBlockFPs,见下文。

splitPackedValues

  splitPackedValues 是一个字节数组,该值用来描述每一个节点使用哪个维度(维度编号)进行划分以及维度的值(在本篇文章中暂时不用理解这段话,在后面的文章中展开介绍),在当前流程点,我们只需要知道该数组的初始化的时机点,初始化的代码很简单,故直接给出:

1
final byte[] splitPackedValues = new byte[numLeaves * (bytesPerDim + 1)];

  上述代码中,numLeavs 为即将构建的 BKD 树中的叶子节点的数量,bytesPerDim 的值为每个维度的值占用的字节数量,例如 int 类型的维度值占用 4 个字节(见文章 索引文件的生成(八)之 dim&&dii 中关于数值类型转为为字节类型的介绍)。

leafBlockFPs

  leafBlockFPs 是一个 long 类型的数组,在当前流程点被初始化,如下所示:

1
final long[] leafBlockFPs = new long[numLeaves];

  leafBlockFPs 在随后的流程中会记录每一个叶子节点的信息在索引文件。dim 中的起始位置,如下所示:

图 3:

  图 2 中的 LeafNodeData 描述的是每个叶子节点的信息,可见 leafBlockFPs 数组中的数组元素数量为叶子节点的数量,即上文中的 numLeaves。

docsSeen

  docsSeen 是一个 FixedBitSet 对象,用来 去重记录 包含当前点数据域的文档的数量,例如我们添加下面两篇文档:

图 4:

  图 3 中,尽管有 3 条点数据内容,但是文档 1 中包含了 2 条,那么包含域名为"content"的点数据的文档的数量为 2,docsSeen 中统计的文档数量在后面的流程会被写入到索引文件。dim 中,如下红框所示:

图 5:

parentSplits

  parentSplits 是一个 int 类型的数组,首先看下初始化这个数组的源码:

1
final int[] parentSplits = new int[numDims];

  上述源码中,numDims 指的是当前处理的点数据的维度数,例如图 3 中处理的是三维的点数据,那么 numDims 的值为 3。

  在文章 Bkd-Tree 中介绍关于 选出切分维度 的内容时候说到,选择的判断依据如下:

1
条件1. 先计算出切