Lucene文档结构
Lucene
中最基础的概念是索引(
index
),文档(
document
.,域(
field
)和项(
term
)。
索引包含了一个文档的序列。
· 文档是一些域的序列。
· 域是一些项的序列。
· 项就是一个字串。
存在于不同域中的同一个字串被认为是不同的项。因此项实际是用一对字串表示的,第一个字串是域名,第二个是域中的字串。
索引包含了一个文档的序列。
· 文档是一些域的序列。
· 域是一些项的序列。
· 项就是一个字串。
存在于不同域中的同一个字串被认为是不同的项。因此项实际是用一对字串表示的,第一个字串是域名,第二个是域中的字串。
4.1. Lucene概念详细介绍
Lucene
中,域的文本可能以逐字的非倒排的方式存储在索引中。而倒排过的域称为被索引过了。域也可能同时被存储和被索引。
域的文本可能被分解许多项目而被索引,或者就被用作一个项目而被索引。大多数的域是被分解过的,但是有些时候某些标识符域被当做一个项目索引是很有用的。
域的文本可能被分解许多项目而被索引,或者就被用作一个项目而被索引。大多数的域是被分解过的,但是有些时候某些标识符域被当做一个项目索引是很有用的。
4.1.2. 段(Segment)
Lucene
索引可能由多个子索引组成,这些子索引成为段。每一段都是完整独立的索引,能被搜索。索引是这样作成的:
1. 为新加入的文档创建新段。
2. 合并已经存在的段。
搜索时需要涉及到多个段和 / 或者多个索引,每一个索引又可能由一些段组成。
1. 为新加入的文档创建新段。
2. 合并已经存在的段。
搜索时需要涉及到多个段和 / 或者多个索引,每一个索引又可能由一些段组成。
4.1.3. 文档号(document.nbspNumber)
内部的来说,
Lucene
用一个整形(
interger
)的文档号来指示文档。第一个被加入到索引中的文档就是
0
号,顺序加入的文档将得到一个由前一个号码递增而来的号码。
注意文档号是可能改变的,所以在
Lucene
外部存储这些号码时必须小心。特别的,号码的改变的情况如下:
· 只有段内的号码是相同的,不同段之间不同,因而在一个比段广泛的上下文环境中使用这些号码时,就必须改变它们。标准的技术是根据每一段号码多少为每一段分配一个段号。将段内文档号转换到段外时,加上段号。将某段外的文档号转换到段内时,根据每段中可能的转换后号码范围来判断文档属于那一段,并减调这一段的段号。例如有两个含 5 个文档的段合并,那么第一段的段号就是 0 ,第二段段号 5 。第二段中的第三个文档,在段外的号码就是 8 。
· 文档删除后,连续的号码就出现了间断。这可以通过合并索引来解决,段合并时删除的文档相应也删掉了,新合并而成的段并没有号码间断。
· 只有段内的号码是相同的,不同段之间不同,因而在一个比段广泛的上下文环境中使用这些号码时,就必须改变它们。标准的技术是根据每一段号码多少为每一段分配一个段号。将段内文档号转换到段外时,加上段号。将某段外的文档号转换到段内时,根据每段中可能的转换后号码范围来判断文档属于那一段,并减调这一段的段号。例如有两个含 5 个文档的段合并,那么第一段的段号就是 0 ,第二段段号 5 。第二段中的第三个文档,在段外的号码就是 8 。
· 文档删除后,连续的号码就出现了间断。这可以通过合并索引来解决,段合并时删除的文档相应也删掉了,新合并而成的段并没有号码间断。
4.1.4. 索引信息
索引段维护着以下的信息:
· 域集合。包含了索引中用到的所有的域。
· 域值存储表。每一个文档都含有一个“属性-值”对的列表,属性即为域名。这个列表用来存储文档的一些附加信息,如标题, url 或者访问数据库的一个 ID 。在搜索时存储域的集合可以被返回。这个表以文档号标识。
· 项字典。这个字典含有所有文档的所有域中使用过的的项,同时含有使用过它的文档的文档号,以及指向使用频数信息和位置信息的指针。
· 项频数信息。对于项字典中的每个项,这些信息包含含有这个项的文档的总数,以及每个文档中使用的次数。
· 项位置信息。对于项字典中的每个项,都存有在每个文档中出现的各个位置。
· 标准化因子。对于文档中的每一个域,存有一个值,用来以后乘以这个这个域的命中数( hits )。
· 被删除的文档信息。这是一个可选文件,用来表明那些文档已经删除了。
接下来的各部分部分详细描述这些信息。
· 域集合。包含了索引中用到的所有的域。
· 域值存储表。每一个文档都含有一个“属性-值”对的列表,属性即为域名。这个列表用来存储文档的一些附加信息,如标题, url 或者访问数据库的一个 ID 。在搜索时存储域的集合可以被返回。这个表以文档号标识。
· 项字典。这个字典含有所有文档的所有域中使用过的的项,同时含有使用过它的文档的文档号,以及指向使用频数信息和位置信息的指针。
· 项频数信息。对于项字典中的每个项,这些信息包含含有这个项的文档的总数,以及每个文档中使用的次数。
· 项位置信息。对于项字典中的每个项,都存有在每个文档中出现的各个位置。
· 标准化因子。对于文档中的每一个域,存有一个值,用来以后乘以这个这个域的命中数( hits )。
· 被删除的文档信息。这是一个可选文件,用来表明那些文档已经删除了。
接下来的各部分部分详细描述这些信息。
4.1.5. 文件的命名(File Naming)
同属于一个段的文件拥有相同的文件名,不同的扩展名。扩展名由以下讨论的各种文件格式确定。
一般来说,一个索引存放一个目录,其所有段都存放在这个目录里,不这样作,也是可以的,在性能方面较低。
一般来说,一个索引存放一个目录,其所有段都存放在这个目录里,不这样作,也是可以的,在性能方面较低。
4.2. Lucene基本数据类型(Primitive Types)
最基本的数据类型就是字节(
byte
,
8
位)。文件就是按字节顺序访问的。其它的一些数据类型也定义为字节的序列,文件的格式具有字节意义上的独立性。
UInt32 : 32 位无符号整数,由四个字节组成,高位优先。 UInt32 --> <Byte>4
Uint64 : 64 位无符号整数,由八字节组成,高位优先。 UInt64 --> <Byte>8
VInt : 可变长的正整数类型,每字节的最高位表明还剩多少字节。每字节的低七位表明整数的值。因此单字节的值从 0 到 127 ,两字节值从 128 到 16,383 ,等等。
VInt 编码示例
value
First byte
Second byte
Third byte
0
00000000
1
00000001
2
00000010
...
127
01111111
128
10000000
00000001
129
10000001
00000001
130
10000010
00000001
...
16,383
11111111
01111111
16,384
10000000
10000000
00000001
16,385
10000001
10000000
00000001
... 这种编码提供了一种在高效率解码时压缩数据的方法。
UInt32 : 32 位无符号整数,由四个字节组成,高位优先。 UInt32 --> <Byte>4
Uint64 : 64 位无符号整数,由八字节组成,高位优先。 UInt64 --> <Byte>8
VInt : 可变长的正整数类型,每字节的最高位表明还剩多少字节。每字节的低七位表明整数的值。因此单字节的值从 0 到 127 ,两字节值从 128 到 16,383 ,等等。
VInt 编码示例
value
First byte
Second byte
Third byte
0
00000000
1
00000001
2
00000010
...
127
01111111
128
10000000
00000001
129
10000001
00000001
130
10000010
00000001
...
16,383
11111111
01111111
16,384
10000000
10000000
00000001
16,385
10000001
10000000
00000001
... 这种编码提供了一种在高效率解码时压缩数据的方法。
Lucene
输出
UNICODE
字符序列,使用标准
UTF-8
编码。
String : Lucene 输出由 VINT 和字符串组成的字串, VINT 表示字串长,字符串紧接其后。
String --> VInt, Chars
String : Lucene 输出由 VINT 和字符串组成的字串, VINT 表示字串长,字符串紧接其后。
String --> VInt, Chars
索引中活动的段存储在
Segments
文件中。每个索引只能含有一个这样的文件,名为
"segments".
这个文件依次列出每个段的名字和每个段的大小。
Segments --> SegCount, <SegName, SegSize>SegCount
SegCount, SegSize --> UInt32
SegName --> String
SegName 表示该 segment 的名字,同时作为索引其他文件的前缀。
SegSize 是段索引中含有的文档数。
Segments --> SegCount, <SegName, SegSize>SegCount
SegCount, SegSize --> UInt32
SegName --> String
SegName 表示该 segment 的名字,同时作为索引其他文件的前缀。
SegSize 是段索引中含有的文档数。
4.3.2. Lock文件
有一些文件用来表示另一个进程在使用索引。
· 如果存在 "commit.lock" 文件,表示有进程在写 "segments" 文件和删除无用的段索引文件,或者表示有进程在读 "segments" 文件和打开某些段的文件。在一个进程在读取 "segments" 文件段信息后,还没来得及打开所有该段的文件前,这个 Lock 文件可以防止另一个进程删除这些文件。
· 如果存在 "index.lock" 文件,表示有进程在向索引中加入文档,或者是从索引中删除文档。这个文件防止很多文件同时修改一个索引。
· 如果存在 "commit.lock" 文件,表示有进程在写 "segments" 文件和删除无用的段索引文件,或者表示有进程在读 "segments" 文件和打开某些段的文件。在一个进程在读取 "segments" 文件段信息后,还没来得及打开所有该段的文件前,这个 Lock 文件可以防止另一个进程删除这些文件。
· 如果存在 "index.lock" 文件,表示有进程在向索引中加入文档,或者是从索引中删除文档。这个文件防止很多文件同时修改一个索引。
4.3.3. Deleteable文件
名为
"deletetable"
的文件包含了索引不再使用的文件的名字,这些文件可能并没有被实际的删除。这种情况只存在与
Win32
平台下,因为
Win32
下文件仍打开时并不能删除。
Deleteable --> DelableCount, <DelableName>DelableCount
DelableCount --> UInt32
DelableName --> String
Deleteable --> DelableCount, <DelableName>DelableCount
DelableCount --> UInt32
DelableName --> String
4.3.4. 段包含的文件(Per-Segment Files)
剩下的文件是每段中包含的文件,因此由后缀来区分。
域( Field )
域集合信息( Field Info )
所有域名都存储在这个文件的域集合信息中,这个文件以后缀 .fnm 结尾。
FieldInfos (.fnm) --> FieldsCount, <FieldName, FieldBits>FieldsCount
FieldsCount --> VInt
FieldName --> String
FieldBits --> Byte
目前情况下, FieldBits 只有使用低位,对于已索引的域值为 1 ,对未索引的域值为 0 。
文件中的域根据它们的次序编号。因此域 0 是文件中的第一个域,域 1 是接下来的,等等。这个和文档号的编号方式相同。
域( Field )
域集合信息( Field Info )
所有域名都存储在这个文件的域集合信息中,这个文件以后缀 .fnm 结尾。
FieldInfos (.fnm) --> FieldsCount, <FieldName, FieldBits>FieldsCount
FieldsCount --> VInt
FieldName --> String
FieldBits --> Byte
目前情况下, FieldBits 只有使用低位,对于已索引的域值为 1 ,对未索引的域值为 0 。
文件中的域根据它们的次序编号。因此域 0 是文件中的第一个域,域 1 是接下来的,等等。这个和文档号的编号方式相同。
域值存储表使用两个文件表示:
1. 域索引( .fdx 文件)。
如下,对于每个文档这个文件包含指向域值的指针:
FieldIndex (.fdx) --> <FieldvaluesPosition>SegSize
FieldvaluesPosition --> Uint64
FieldvaluesPosition 指示的是某一文档的某域的域值在域值文件中的位置。因为域值文件含有定长的数据信息,因而很容易随机访问。在域值文件中,文档 n 的域值信息就存在 n*8 位置处( The position of document . nbspn's field data is the Uint64 at n*8 in this file. )。
2. 域值( .fdt 文件)。
如下,每个文档的域值信息包含:
FieldData (.fdt) --> <DocFieldData>SegSize
DocFieldData --> FieldCount, <FieldNum, Bits, value>FieldCount
FieldCount --> VInt
FieldNum --> VInt
Bits --> Byte
value --> String
目前情况下, Bits 只有低位被使用,值为 1 表示域名被分解过,值为 0 表示未分解过。÷
1. 域索引( .fdx 文件)。
如下,对于每个文档这个文件包含指向域值的指针:
FieldIndex (.fdx) --> <FieldvaluesPosition>SegSize
FieldvaluesPosition --> Uint64
FieldvaluesPosition 指示的是某一文档的某域的域值在域值文件中的位置。因为域值文件含有定长的数据信息,因而很容易随机访问。在域值文件中,文档 n 的域值信息就存在 n*8 位置处( The position of document . nbspn's field data is the Uint64 at n*8 in this file. )。
2. 域值( .fdt 文件)。
如下,每个文档的域值信息包含:
FieldData (.fdt) --> <DocFieldData>SegSize
DocFieldData --> FieldCount, <FieldNum, Bits, value>FieldCount
FieldCount --> VInt
FieldNum --> VInt
Bits --> Byte
value --> String
目前情况下, Bits 只有低位被使用,值为 1 表示域名被分解过,值为 0 表示未分解过。÷
4.3.6. 项字典(Term Dictionary)
项字典用以下两个文件表示:
1. 项信息( .tis 文件)。
TermInfoFile (.tis)--> TermCount, TermInfos
TermCount --> UInt32
TermInfos --> <TermInfo>TermCount
TermInfo --> <Term, DocFreq, FreqDelta, ProxDelta>
Term --> <PrefixLength, Suffix, FieldNum>
Suffix --> String
PrefixLength, DocFreq, FreqDelta, ProxDelta
--> VInt
1. 项信息( .tis 文件)。
TermInfoFile (.tis)--> TermCount, TermInfos
TermCount --> UInt32
TermInfos --> <TermInfo>TermCount
TermInfo --> <Term, DocFreq, FreqDelta, ProxDelta>
Term --> <PrefixLength, Suffix, FieldNum>
Suffix --> String
PrefixLength, DocFreq, FreqDelta, ProxDelta
--> VInt
项信息按项排序。项信息排序时先按项所属的域的文字顺序排序,然后按照项的字串的文字顺序排序。
项的字前缀往往是共同的,与字的后缀组成字。 PrefixLength 变量就是表示与前一项相同的前缀的字数。因此,如果前一个项的字是 "bone" ,后一个是 "boy" 的话, PrefixLength 值为 2 , Suffix 值为 "y" 。
项的字前缀往往是共同的,与字的后缀组成字。 PrefixLength 变量就是表示与前一项相同的前缀的字数。因此,如果前一个项的字是 "bone" ,后一个是 "boy" 的话, PrefixLength 值为 2 , Suffix 值为 "y" 。
FieldNum
指明了项属于的域号,而域名存储在
.fdt
文件中。
DocFreg
表示的是含有该项的文档的数量。
FreqDelta
指明了项所属
TermFreq
变量在
.frq
文件中的位置。详细的说,就是指相对于前一个项的数据的位置偏移量(或者是
0
,表示文件中第一个项)。
ProxDelta
指明了项所属的
TermPosition
变量在
.prx
文件中的位置。详细的说,就是指相对于前一个项的数据的位置偏移量(或者是
0
,表示文件中第一个项)。
2.
项信息索引(
.tii
文件)。
每个项信息索引文件包含
.tis
文件中的
128
个条目,依照条目在
.tis
文件中的顺序。这样设计是为了一次将索引信息读入内存能,然后使用它来随机的访问
.tis
文件。
这个文件的结构和 .tis 文件非常类似,只在每个条目记录上增加了一个变量 IndexDelta 。
TermInfoIndex (.tii)--> IndexTermCount, TermIndices
IndexTermCount --> UInt32
TermIndices --> <TermInfo, IndexDelta>IndexTermCount
IndexDelta --> VInt
IndexDelta 表示该项的 TermInfo 变量值在 .tis 文件中的位置。详细的讲,就是指相对于前一个条目的偏移量(或者是 0 ,对于文件中第一个项)。
这个文件的结构和 .tis 文件非常类似,只在每个条目记录上增加了一个变量 IndexDelta 。
TermInfoIndex (.tii)--> IndexTermCount, TermIndices
IndexTermCount --> UInt32
TermIndices --> <TermInfo, IndexDelta>IndexTermCount
IndexDelta --> VInt
IndexDelta 表示该项的 TermInfo 变量值在 .tis 文件中的位置。详细的讲,就是指相对于前一个条目的偏移量(或者是 0 ,对于文件中第一个项)。
.frq
文件包含每一项的文档的列表,还有该项在对应文档中出现的频数。
FreqFile (.frq) --> <TermFreqs>TermCount
TermFreqs --> <TermFreq>DocFreq
TermFreq --> DocDelta, Freq?
DocDelta,Freq --> VInt
TermFreqs 序列按照项来排序(依据于 .tis 文件中的项,即项是隐含存在的)。
TermFreq 元组按照文档号升序排列。
DocDelta 决定了文档号和频数。详细的说, DocDelta/2 表示相对于前一文档号的偏移量(或者是 0 ,表示这是 TermFreqs 里面的第一项)。当 DocDelta 是奇数时表示在该文档中频数为 1 ,当 DocDelta 是偶数时,另一个 VInt ( Freq )就表示在该文档中出现的频数。
例如,假设某一项在文档 7 中出现一次,在文档 11 中出现了 3 次,在 TermFreqs 中就存在如下的 VInts 序列:
15, 22, 3
FreqFile (.frq) --> <TermFreqs>TermCount
TermFreqs --> <TermFreq>DocFreq
TermFreq --> DocDelta, Freq?
DocDelta,Freq --> VInt
TermFreqs 序列按照项来排序(依据于 .tis 文件中的项,即项是隐含存在的)。
TermFreq 元组按照文档号升序排列。
DocDelta 决定了文档号和频数。详细的说, DocDelta/2 表示相对于前一文档号的偏移量(或者是 0 ,表示这是 TermFreqs 里面的第一项)。当 DocDelta 是奇数时表示在该文档中频数为 1 ,当 DocDelta 是偶数时,另一个 VInt ( Freq )就表示在该文档中出现的频数。
例如,假设某一项在文档 7 中出现一次,在文档 11 中出现了 3 次,在 TermFreqs 中就存在如下的 VInts 序列:
15, 22, 3
4.3.8. 项位置(Position)
.prx
文件包含了某文档中某项出现的位置信息的列表。
ProxFile (.prx) --> <TermPositions>TermCount
TermPositions --> <Positions>DocFreq
Positions --> <PositionDelta>Freq
PositionDelta --> VInt
TermPositions 按照项来排序(依据于 .tis 文件中的项,即项是隐含存在的)。
Positions 元组按照文档号升序排列。
PositionDelta 是相对于前一个出现位置的偏移位置(或者为 0 ,表示这是第一次在这个文档中出现)。
例如,假设某一项在某文档第 4 项出现,在另一个文档中第 5 项和第 9 项出现,将存在如下的 VInt 序列:
4, 5, 4
ProxFile (.prx) --> <TermPositions>TermCount
TermPositions --> <Positions>DocFreq
Positions --> <PositionDelta>Freq
PositionDelta --> VInt
TermPositions 按照项来排序(依据于 .tis 文件中的项,即项是隐含存在的)。
Positions 元组按照文档号升序排列。
PositionDelta 是相对于前一个出现位置的偏移位置(或者为 0 ,表示这是第一次在这个文档中出现)。
例如,假设某一项在某文档第 4 项出现,在另一个文档中第 5 项和第 9 项出现,将存在如下的 VInt 序列:
4, 5, 4
4.3.9. 标准化因子(Normalization Factor)
.nrm
文件包含了每个文档的标准化因子,标准化因子用来以后乘以这个这个域的命中数。
Norms (.nrm) --> <Byte>SegSize
每个字节记录一个浮点数。位 0-2 包含了 3 位的尾数部分,位 3-8 包含了 5 位的指数部分。
按如下规则可将这些字节转换为 IEEE 标准单精度浮点数:
1. 如果该字节是 0 ,就是浮点 0 ;
2. 否则,设置新浮点数的标志位为 0 ;
3. 将字节中的指数加上 48 后作为新的浮点数的指数;
4. 将字节中的尾数映射到新浮点数尾数的高 3 位;并且
5. 设置新浮点数尾数的低 21 位为 0 。
Norms (.nrm) --> <Byte>SegSize
每个字节记录一个浮点数。位 0-2 包含了 3 位的尾数部分,位 3-8 包含了 5 位的指数部分。
按如下规则可将这些字节转换为 IEEE 标准单精度浮点数:
1. 如果该字节是 0 ,就是浮点 0 ;
2. 否则,设置新浮点数的标志位为 0 ;
3. 将字节中的指数加上 48 后作为新的浮点数的指数;
4. 将字节中的尾数映射到新浮点数尾数的高 3 位;并且
5. 设置新浮点数尾数的低 21 位为 0 。
4.3.10. 被删除的文档(Deleted document)
.del
文件是可选的,只有在某段中存在删除操作后才存在:
Deletions (.del) --> ByteCount,BitCount,Bits
ByteSize,BitCount --> Uint32
Bits --> <Byte>ByteCount
ByteCount 表示的是 Bits 列表中 Byte 的数量。典型的,它等于( SegSize/8 ) +1 。
BitCount 表示 Bits 列表中多少个已经被设置过了。
Bits 列表包含了一些位( bit ),顺序表示一个文档。当对应于文档号的位被设置了,就标志着这个文档已经被删除了。位的顺序是从低到高。因此,如果 Bits 包含两个字节, 0x00 和 0x02 ,那么表示文档 9 已经删除了。
Deletions (.del) --> ByteCount,BitCount,Bits
ByteSize,BitCount --> Uint32
Bits --> <Byte>ByteCount
ByteCount 表示的是 Bits 列表中 Byte 的数量。典型的,它等于( SegSize/8 ) +1 。
BitCount 表示 Bits 列表中多少个已经被设置过了。
Bits 列表包含了一些位( bit ),顺序表示一个文档。当对应于文档号的位被设置了,就标志着这个文档已经被删除了。位的顺序是从低到高。因此,如果 Bits 包含两个字节, 0x00 和 0x02 ,那么表示文档 9 已经删除了。
4.3.11. 局限性(Limitations)
在以上的文件格式中,好几处都有限制项和文档的最大个数为
32
位数的极限,即接近于
40
亿。今天看来,这不会造成问题,但是,长远的看,可能造成问题。因此,这些极限应该或者换为
UInt64
类型的值,或者更好的,换为
VInt
类型的值(
VInt
值没有上限)。
有两处地方的代码要求必须是定长的值,他们是:
1. FieldvaluesPosition
变量(存储于域索引文件中,
.fdx
文件)。它已经是一个
UInt64
型,所以不会有问题。
2. TermCount 变量(存储于项信息文件中, .tis 文件)。这是最后输出到文件中的,但是最先被读取,因此是存储于文件的最前端 。索引代码先在这里写入一个 0 值,然后在其他文件输出完毕后覆盖这个值。所以无论它存储在什么地方,它都必须是一个定长的值,它应该被变成 UInt64 型。
除此之外,所有的 UInt 值都可以换成 VInt 型以去掉限制。
2. TermCount 变量(存储于项信息文件中, .tis 文件)。这是最后输出到文件中的,但是最先被读取,因此是存储于文件的最前端 。索引代码先在这里写入一个 0 值,然后在其他文件输出完毕后覆盖这个值。所以无论它存储在什么地方,它都必须是一个定长的值,它应该被变成 UInt64 型。
除此之外,所有的 UInt 值都可以换成 VInt 型以去掉限制。