Lucene 段文件(segment, si)格式分析
以下均是基于lucene 8.1.0
1. 文件格式
段元数据解释:
- 1到8节是一个整型CODEC_MAGIC数值为:
0x3f d7 6c 17
- 接下来0x08是一个退格, 看代码其实没有这个,但不知为啥有这个
- 接下来为'segment' 这个都是可见字符,如右图
- 接下来是一个版本号,可以知道版本号为
0x00 00 00 09
, 即9, 目前来说要求该版本号在VERSION_70(7)到VERSION_74(9)之间 - 接着是16字节的id字段,即
0x88 b000 65f0 baed e64b feb6 9c0b 9f3c 98
- 接着是一个字节的
IndexHeaderSuffix
, 值为0x01, 即1 - 然后读取7的
IndexHeaderSuffix
字节数,即 0x33, 对应数据3, 即后缀为3 - 接下来读取3个变长的int数, 即
0x08
,0x01
,0x00
即8、1、0, 代表lucene主版本号、次版本号、bugfix版本号 - 接下来读取一个可变Int的
indexCreatedVersion
, 如图会读取到0x08, 即8 - 接下来会读取一个8字节的Long值,该值代码SegmentInfos的版本号,这个版本号代表着index 被修改了多少次, 如图会读到
0x0000 0000 0000 000a
即10 - 如果在步中读到的值 大于 VERSION_70(7)的话,即接下读一个可变long值, 否则的话一个int值,由于是基于lucene 8测试,此时肯定读一个long, 即
0x03
即3, 这个counter用于命名新的segments - 接下来读一个int的 segments数量,读到
00 00 00 03
即3 - 接下下读3个可变int, 类似于9中, 用于指名segmentInfors的
minSegmentLuceneVersion
的值, 如图,会依次读到0x08
,0x01
,0x00
, 即8,1,0 对应lucene 版本为 8.1.0 - 现在开始一次读取这三个段的内容, 此部分以一个循环的行为读取
- 14.1 首先读取段的名称
segmentName
, lucence的DataInput
存储字符串的方式是: 用一个vint 存储长度,接下来是实际的字符串内容。首先读vint会读到0x02
即2, 长度为2,接下来读取0x5f30
即'_0', 即segmentName是_0
- 14.2 接下来读到16个字节的id
0x2372 32a1 0510 310f 9913 0f21 8aec fd1f
- 14.3 接下来读到一个字符串, 会读取
Lucene80
(0x08 4c 7563 656e 6538 30
), 接下来会通过SPI反射拿到CodeC, 类名org.apache.lucene.codecs.lucene80.Lucene80Codec
, 接下来会调用CodeC的segmentInfoFormat
的read方法读取segment信息。 这里需要仔细展开。- 14.3.1 首先得到段信息文件
_0.si
打开文件,_0.si
的内容如下:
- 14.3.1 首先得到段信息文件
-
14.3.2 检查文件头部。
首先读到一个Int的CODEC_MAGIC (0x3f d7 6c 17
), 这里会针对这个作判断以验证文件是否已被破坏。接着读取一个19字节的actualCodec
字段,如图会读取Lucene70SegmentInfo
, 接下来会读取一个Int的actualVersion
这会读到0。 接下来读取16个字段的id0x2372 32a1 0510 310f 9913 0f21 8aec fd1f
, 再接着读到一个字节以作为IndexHeaderSuffix
的长度,会读到0, 再接着会读0字节的IndexHeaderSuffix
的值;再接着会连续读3个Int值代码的主、次、bugfix版本号。依次读到8, 1,0
接下来会读一个字节的minVersion, 从而会读到0x01
即1, 如果 minVersion = 1的话接下来会依次读三个int, 即会读到0x0000 0008
,0x0000 0001
,0x00000000
-
14.3.3 读取文件实际内容
接上,读完minVersion后,会读一个int的docCount, 代表在这个段内doc的数量,会读到0x0000 0001
即1,接着会读一个字节的的标识, 会读到0x01
, 如果该值与SegmentInfo.YES
的值相等,则代表这是一个复合文件,见isCompoundFile = true
. 接着会读取Map<String, String>, Set<String>, Map<String, String>读取第一个Map逻辑(详细请参考
DataInput.readMapOfStrings()
): 首先读取一个可变int值代表map的size, 如图会读到0x0a
, 即10,接着依次读取10个keyValue对, 会读到。key length key(0x) key(char) value length value(0x) value(char) 0x02 0x6f73 os 0x08 0x4d61 6320 4f53 2058 Mac OS X 0x0b 0x6a6176612e76656e646f72 java.vendor 0x12 0x4f 7261 636c 6520 436f 7270 6f72 6174 696f 6e Oracle Corporation 0x0c 0x 6a61 7661 2e76 6572 7369 6f6e java.version 0x09 0x31 2e38 2e30 5f31 3531 1.8.0_151 0x0f 0x 6a 6176 612e 766d 2e76 6572 7369 6f6e java.vm.version 0x0a 0x32 352e 3135 312d 6231 32 25.151-b12 0x0e 0x6c75 6365 6e65 2e76 6572 7369 6f6e lucene.version 0x05 38 2e31 2e30 8.1.0 0x07 0x6f 732e 6172 6368 os.arch 0x06 0x 78 3836 5f36 34 x86_64 0x14 0x6a61 7661 2e72 756e 7469 6d65 2e76 6572 7369 6f6e java.runtime.version 0x0d 0x 31 2e38 2e30 5f31 3531 2d62 3132 1.8.0_151-b12 0x06 0x73 6f75 7263 65 source 0x05 666c 7573 68 flush 0x0a 0x 6f73 2e76 6572 7369 6f6e os.version 0x07 31 302e 3133 2e31 10.13.1 0x09 0x 74 696d 6573 7461 6d70 timestamp 0x0d 0x31 3536 3337 3033 3136 3834 3132 1563703168412 读Set: 首先读取一个可变Int做为Set的size, 会读到
0x03
, 接着会依次读到length(0x) value(0x) value(char) 0x06 0x5f30 2e63 6665 _0.cfe 0x05 0x5f 303e 7369 _0.si 0x06 0x5f30 2e63 6673 _0.cfs
再次读Map,首先读取map的size = 0x01
, 接着
key length | key(0x) | key(char) | value length | value(0x) | value(char) |
---|---|---|---|---|---|
0x1f | 0x 4c 7563 656e 6535 3053 746f 7265 6446 6965 6c64 7346 6f72 6d61 742e 6d6f 6465 | Lucene50StoredFieldsFormat.mode | 0a | 0x4553 545f 5350 4545 44 | BEST_SPEED |
后续: 读一个可变Int 以作为 numSortFields, 如图会读到0x00
, 如果此值不为0, 会对这一字段做进一步解析()。 后面会是文件尾。文件尾长度为16个字节, 依次为 尾Magic, 值为CODEC_MAGIC的反码(0xc028 93e8
) 接着会读一个int的algorithmID
值 0x0000 0000
。 接下来会读一个long类型的checksum 0x0000 0000 e440 e97d
最后会剩下一个0x0a
回到segment_3文件。
接下来会依次读:
一个Long的delGen
, 值0x ff ffff ffff ffff ff
, 再读一个Int的delCount
,值为0x00 0000 00
一个Long的fieldInfosGen
值为0xff ffff ffff ffff ff
. 一个Long的 dvGen 值为0x ff ffff ffff ffff ff
一个Int的 softDelCount, 值为00 0000 00
后会就是文件尾,和_0.si
文件格式一致,请参考此块文件内容。