lucene倒排表在内存中的缓存结构2--一个demo
跟进TermHashPerField里面的add()方法,
新增term
为term即将存储的[docId,freq]信息、posi等信息,在bytePool中申请slice(内存空间),并将对应的slice起始位置作为[docId,freq]和posi等信息的结束位置写入intPool(由于还没存入信息,所以用起始位置作为结束位置),两个信息在bytePool中分别存在独立的slice中。
调用FreqProxTermsWriterPerField的newTerm方法,首先将该term的lastdocId置为当前docId,将freq置为1,将docCodes置为当前docId << 1,左移一位目的是,最后一位为0,表示后面跟随freq信息,在addTerm时可以看到其他处理,这个优化是因为大多数term都只会出现一次,另开一个int存储比较浪费。
然后在bytePool中写入posi等信息,并调整intPool中posi信息的最后一位下标。
已有term
调用FreqProxTermsWriterPerField的addTerm方法,首先判断当前处理的docId和该term最后一次处理的docId是否一样,如果一样,则证明这是一个doc分词出的相同term,需要累加freq,但是不需要更新docId;如果不一样,则证明上一次的doc已经处理完毕,应当将上次的所有信息刷入内存池,我们以不一样为例讲解下。
如果不是一个docId,则证明上一个文档刚处理结束,当前所有记录的信息都是上一个doc的。如果出现频率的频率等于1,则没必要写入freq信息,直接把docCodes最后一位置为1,写入docCodes即可。否则,直接写入docCodes(此时docCodes最后一位为0,在newTerm的时候有设置),并且写入freq信息。
写入完成后,则上一个doc处理完毕,开始处理当前文档。首先将termFreq设置为1,表明这是当前文档第一次出现这个term,然后设置docCodes,采用差值设置,并左移一位,将最后一位置为0,原理同newTerm。
然后写入posi等信息,原理通newTerm。
测试demo
private Document getDocument(String value) throws Exception {
Document doc = new Document();
FieldType fieldType = new FieldType();
fieldType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
fieldType.setTokenized(true);
Field pathField = new Field("name", value, fieldType);
//向document中添加信息
doc.add(pathField);
return doc;
}
//创建索引
public void writeToIndex() throws Exception {
//需要创建索引的数据位置
Document document = getDocument("lucene1");
writer.addDocument(document);
// breakpoint1
document = getDocument("lucene2 lucene2");
writer.addDocument(document);
// breakpoint2
document = getDocument("lucene2 lucene2 test lucene2 lucene2");
writer.addDocument(document);
// breakpoint3
}
breakpoint1
下标 postingsArray.textStarts postingsArray.intStarts postindesArray.byteStarts intPool bytePool
0 0 0 8 8 7
1 0 0 0 14 108
2 0 0 0 0 117
3 0 0 0 0 99
4 0 0 0 0 101
5 0 0 0 0 110
6 0 0 0 0 101
7 0 0 0 0 49
8 0 0 0 0 0
9 0 0 0 0 0
10 0 0 0 0 0
11 0 0 0 0 0
12 0 0 0 0 16
13 0 0 0 0 0
14 0 0 0 0 0
15 0 0 0 0 0
16 0 0 0 0 0
17 0 0 0 0 16
在这个断点,只有一个term出现,lucene1的termId为0。
textStarts[0] = 0,表示term字面值在bytePool中第0位开始,bytePool[0] = 7,表示term长度为7,bytePool中1~7为term字面值。
8~12是第一个slice,用来存储[docId,freq],最后一位16表示没有向后延伸。
13~17是第二个slice,用来存储posi等信息,最后一位16表示没有向后延伸。
再来看intStarts[0] = 0,表示term相关信息在intPool中第0位开始,由于有posi信息,则在intPool中需要占两个位置。因此intPool[0]和intPool[1]分别表示这个term在bytePool中[docId,freq]和posi等信息的结束位置+1
byteStarts[0] = 8,表示term的[docId,freq]信息在bytePool中从第8个字节开始。
intPool[0] = 8,表示[docId,freq]在bytePool中结束位置 + 1 。为什么明明有一个doc,但是intPool[0]中指示[doc,freq]的结束位置为8,等于byteStarts[0]呢,相当于没有任何信息呢?原因是虽然doc1已经处理完毕,但是此时对于lucene1这个term,没有其他的doc,所以这个信息还没有被写入intPool,仍存在lucene1的这个term的docCodes、freq数组中。
intPool[1] = 14,表示pos等信息的结束位置为14,这个信息的长度可以通过[docId,freq]的数量计算出来,分词后的每一个term都会存这个信息,因此这个信息长度为sum(freq)。这里可以看到值为0。这个要分两部分看,二进制最后一位为0,表示没有后续信息,前7位为0,表示term在这个field原生值分词后的第一位。
到这里,breakpoint1的所有信息都分析完毕
breakpoint2
下标 postingsArray.textStarts postingsArray.intStarts postindesArray.byteStarts intPool bytePool
0 0 0 8 8 7
1 18 2 26 14 108
2 0 0 0 26 117
3 0 0 0 33 99
4 0 0 0 0 101
5 0 0 0 0 110
6 0 0 0 0 101
7 0 0 0 0 49
8 0 0 0 0 0
9 0 0 0 0 0
10 0 0 0 0 0
11 0 0 0 0 0
12 0 0 0 0 16
13 0 0 0 0 0
14 0 0 0 0 0
15 0 0 0 0 0
16 0 0 0 0 0
17 0 0 0 0 16
18 0 0 0 0 7
19 0 0 0 0 108
20 0 0 0 0 117
21 0 0 0 0 99
22 0 0 0 0 101
23 0 0 0 0 110
24 0 0 0 0 101
25 0 0 0 0 50
26 0 0 0 0 0
27 0 0 0 0 0
28 0 0 0 0 0
29 0 0 0 0 0
30 0 0 0 0 16
31 0 0 0 0 0
32 0 0 0 0 2
33 0 0 0 0 0
34 0 0 0 0 0
35 0 0 0 0 16
在这个断点,lucene2的termId为1。
textStarts[1] = 18,表示term字面值在bytePool中第18位开始,bytePool[18] = 7,表示term长度为7,bytePool中19~25为term字面值。
26~30是第一个slice,用来存储[docId,freq],最后一位16表示没有向后延伸。
31~35是第二个slice,用来存储posi等信息,最后一位16表示没有向后延伸。
再来看intStarts[1] = 2,表示term相关信息在intPool中第2位开始,由于有posi信息,则在intPool中需要占两个位置。因此intPool[2]和intPool[3]分别表示这个term在bytePool中[docId,freq]和posi等信息的结束位置+1
byteStarts[1] = 26,表示term的[docId,freq]信息在bytePool中从第26个字节开始。
intPool[2] = 26,表示[docId,freq]在bytePool中结束位置 + 1 。为什么等于byteStarts[1],原因同lucene1
intPool[3] = 33,表示pos等信息的结束位置为3。可以看到bytePool[31] = 0,表示在分词列表中出现的位置是0,后面不跟随其他信息,bytePool[32] = 2,表示在分词列表中出现的位置是1,后面不跟随其他信息。
到这里,breakpoint2的所有信息都分析完毕。
breakpoint3
下标 postingsArray.textStarts postingsArray.intStarts postindesArray.byteStarts intPool bytePool
0 0 0 8 8 7
1 18 2 26 14 108
2 36 4 41 28 117
3 0 0 0 56 99
4 0 0 0 41 101
5 0 0 0 47 110
6 0 0 0 0 101
7 0 0 0 0 49
8 0 0 0 0 0
9 0 0 0 0 0
10 0 0 0 0 0
11 0 0 0 0 0
12 0 0 0 0 16
13 0 0 0 0 0
14 0 0 0 0 0
15 0 0 0 0 0
16 0 0 0 0 0
17 0 0 0 0 16
18 0 0 0 0 7
19 0 0 0 0 108
20 0 0 0 0 117
21 0 0 0 0 99
22 0 0 0 0 101
23 0 0 0 0 110
24 0 0 0 0 101
25 0 0 0 0 50
26 0 0 0 0 2
27 0 0 0 0 2
28 0 0 0 0 0
29 0 0 0 0 0
30 0 0 0 0 16
31 0 0 0 0 0
32 0 0 0 0 0
33 0 0 0 0 0
34 0 0 0 0 0
35 0 0 0 0 51
36 0 0 0 0 4
37 0 0 0 0 116
38 0 0 0 0 101
39 0 0 0 0 115
40 0 0 0 0 116
41 0 0 0 0 0
42 0 0 0 0 0
43 0 0 0 0 0
44 0 0 0 0 0
45 0 0 0 0 16
46 0 0 0 0 4
47 0 0 0 0 0
48 0 0 0 0 0
49 0 0 0 0 0
50 0 0 0 0 16
51 0 0 0 0 2
52 0 0 0 0 0
53 0 0 0 0 2
54 0 0 0 0 4
55 0 0 0 0 2
56 0 0 0 0 0
57 0 0 0 0 0
58 0 0 0 0 0
59 0 0 0 0 0
60 0 0 0 0 0
61 0 0 0 0 0
62 0 0 0 0 0
63 0 0 0 0 0
64 0 0 0 0 17
65 0 0 0 0 0
66 0 0 0 0 0
67 0 0 0 0 0
在这个断点,lucene2是已经出现过的term,会把doc1的信息刷入bytePool,test是新的term,会单独存储并分配slic。
这个field总共会分出5个term:lucene2、lucene2、test、lucene2、lucene2。我们一个个分析信息是如何写入bytePool中的。
第一个lucene2
首先,会发现这是已有的term,termId = 1,addTerm时发现上次的docId是1,这次的docId是2,会先将上次doc的信息刷入bytePool。
上次的docId为1,由于termFreq = 2,需要跟随freq信息,因此将docId左移一位的值直接写入bytePool,然后写入freq,注意freq使用vInt写入的,但是此时freq = 2,只需要一个字节,所以写入的值是2.
向intPool查询当前可以写入的位置,intPool[1] = 26,因此第26个字节写入2表示docId,并且后面跟随freq,第27个字节写入2,表示freq = 2,并设置[docId,freq]结束位置为28。
然后,更新lastDocId等信息,并写入新的term posi等信息。
第二个lucene2
这个没什么好说的,就是正常的addTerm,更新freq,写入posi等信息,freq列表为下标31~34,值为0、2、0、2。
test
新的term出现了,和之前新term处理方式一样,写入term字面值(bytePool下标3640),申请[docId,freq]的splic(4145),申请posi等信息的slice并写入(46~50),写入的值为4,二进制最后一位为0表示不跟随其他信息,右移一位为2表示在分词链中第2个出现,因此posi结束位置为47,[doc,freq]信息还没刷入bytePool,结束位置为41。
第三个lucene2
正常执行addTerm方法,但是在写入posi等信息的时候,要写入的位置是35,这个位置值16表示这是slice的末尾,不能写入值。slice要扩容,并将3234的信息复制到新扩容的区域,重新申请slice得到的slice起始位置为51,将3235四个字节合并表示51,因此3234为0,35表示51,将原本32到34的值复制到5153,因此51~53的置为2、0、2,新的词在分词列表中处于第3位,上一个lucene2处于第1位,采用差值法,应当写入2,左移一位将末尾置0,表示后面没有其他信息,因此54位置写入的值为4。
第四个lucene2
同第二个lucene2,直接在55的位置写入2,将posi信息结束位置修改为53。
到这里,breakpoint3的所有信息都分析完毕