lucene数据写入-02倒排数据缓存组织
上面分析了数据写入的代码逻辑,我们现在来看下倒排数据保存在内存中是什么样的
在索引过程中数据会将term分词的长度、将文档号(doc ID),词频(freq)和位置(prox)信息都存储在bytePool中,其中文档号docID和freqs写到一个块中,而位置信息prox会写到另一个块中,块存储有不同的级别,不同的级别长度和分隔符不同。intPool保存了bytePool中保存的docID、freqs和位置信息的偏移量,所以每个分词在intPool中都会使用两个int保存两个偏移量,第0个表示docid + freq在bytePool中的写入位置偏移量,第1个表示prox在bytePool中的写入位置偏移量。
我们以写入一下数据为例分析一下如何记录这些信息
document.add(new TextField("desc", "common common common common common term", Field.Store.YES));
document1.add(new TextField("desc", "common common common common common term term", Field.Store.YES));
document2.add(new TextField("desc", "term term term common common common common common", Field.Store.YES));
document3.add(new TextField("desc", "term", Field.Store.YES));
- 写入文档0的第一个common
首先会将common的长度和该分词写入到bytePool中,然后分配两个级别为1的块每个块长度为5,结束符号为16,第一个块用来存储common的docID和freqs,docID和freqs信息总是在下一篇文档的处理过程出现了"common"的时候方才写入,因为当一篇文档没有处理完毕的时候,freq也即词频是无法知道的,第二个块用来存储common的位置信息,此处common在第一位所以位置信息就是0,但是此0应该左移一位,最后一位置0表示没有payload存储。
intPool中分配了两个int,第一个int值为7是记录docID和freqs将要写入的偏移量,第二个int值为13是因为已经写入了第一个common的位置信息了,表示的是下一个位置信息写入的偏移量
posting表记录了common的词频和lastPosition信息等。其中intStarts和byteStarts分别记录了读取intPool和bytePool的起始位置
- 写入文档0的第二、三、四个common都和上面类似,bytePool中会写入位置,intPool第二个int会更新,postings中记录的词频和lastPosition会更新
- 写入第三个common
- 写入第四个common
- 写入第五个common
当写入第五个common时候,存储位置信息的快空间不够了已经到达了结束符16,需要进行扩容,新的快层次为2,长度为14,结束符号为17,在缓存的最后分配。原来的块中会腾出4个位置用来存储新位置的指针信息,这里为0 0 0 17,也就是读取的位置是从17开始,将原来结束符前3个字符分别copy到指针之后,也就是将13、14、15的值copy到17、18、19,然后将第五个common的位置写入,同时更新intPool中第二个int为21,posting表中词频和最后位置更新
- 写入文档0的第一个term
bytePool中首先写入term的长度4和term字符串,然后分配两个块,一个块用来存储docID、freqs这里暂时不会写入,另一个块用来存储prox,这里的脚本为5,后面的0表示没有payload,写入值为10,5<<1 + 0 = 10
intPool新分配了两个int用来存储term的docID、freqs和prox在bytePool中的偏移量
postings中更新textStarts记录了分词的起始偏移量,termFreqs词频,lastPosition位置信息,intStarts和byteStarts读取位置
- 写入文档1的第一个common
开始写入上一个文档的common的docID和freqs,上一个文档的docID为0,出现的次数为5,存放的信息为[docid<<1 + 0, 5] = [0, 5],docid左移一位,最后一位为0表示freq大于1,写入到bytePool索引位置为8和9的位置。
然后写入文档1的第一个common的位置信息,0<<1 + 0 = 0,写入到下标为21的位置
posting更新termFreqs、lastDocIDs、lastDocCodes
- 写入文档1的第二个common
- 写入文档1的第三个common
- 写入文档1的第四个common
- 写入文档1的第五个common和写入文档1的第一个term
第一篇文档中的term的docid+freq信息写入,在第一篇文档中term只出现了一次所以只需要一个字节来保存docID和freqs,0|1=1
if (1 == postings.termFreqs[termID]) {
writeVInt(0, postings.lastDocCodes[termID]|1);
} else {
writeVInt(0, postings.lastDocCodes[termID]);
writeVInt(0, postings.termFreqs[termID]);
}
intPool更新第三个int的写入位置记录
postings更新lastDocIDs
写入文档1的第一个term的位置,在第5个位置,5<<1 + 0 = 10
- 写入文档1的第二个term
写入第二个term的位置(fieldState.position-postings.lastPositions[termID])<<1,(6-5)<<1=2
intPool写入prox的偏移量更新
postings中的termFreqs和lastPositions更新
- 写入文档2的第一个term
bytePool写入文档1的term的docID和freqs,[docid<<1 + 0, 2] = [2, 2],docid左移一位,最后一位为0表示freq大于1。
intPool更新第三个块的写入偏移量
postings中的lastDocIDs更新
bytePool写入位置信息0<<1 + 0 = 0
intPool第四个int更新
postings中termFreqs更新、lastPositions更新
- 写入文档2的第二个term
和上面一样存储位置信息的空间不够了,需要进行扩容,新的块级别为2,长度为14,结束符号为17,另外将原来分隔符前三个字节copy到原来的分隔符之后,将空出来的空间已经原来的分隔符构成一个int,保存新的指针信息,这里就是46,然后将位置信息写入
- 写入文档2的第三个term
- 写入文档2的第一个common
docid+freq信息写入,文档1中的common,出现了5次,存储为[docid << 1 + 0, freq],docid取差值为1,因而存储为 [2, 5]
bytePool写入common的位置信息,3<<1+0=6
- 写入文档2的第二个common
- 写入文档2的第三个common
- 写入文档2的第四个common
- 写入文档2的第五个common
存储common的位置信息的空间已经不足,需要进行扩容,分配新的块,层次为3,大小为20,结束符为18, 和上面一样需要将分隔符前三个字节的数据copy到新的位置,另外包括分隔符和前三个空出来的空间组成一个int指针,指向新的读取位置
- 写入文档3的第一个term
term的docID+freqs信息,文档号为2,出现了3次,存储为[docid<<1+0, freq],docid取差值为1,因而存储为[2, 3],但是存储docID和freqs的空间已经满了,一样需要进行扩容,这里新分配的快层级为2,空间长度为14,结束符号为17,重新的尾部分配空间,将原来分隔符前的三个字节copy到新位置,原来的分隔符和空出来的空间组成一个int保存指针信息
写入term的位置信息
所有数据写入完毕最终内存空间如下
postings中主要记录的是分词的读取时候的信息,而intPool主要记录的是docID+freqs和prox在bytePool中的写入位置信息,而bytePool则是记录倒排数据。在flush写入文件的过程中则是一个读取的过程,将这些信息读出来写入.pos文件、.doc文件、.tim文件等