注意:以下文章是参见http://lucene.apache.org/java/3_0_1/fileformats.html#Fields 和实践中读取文件内容概括总结出来的。
Fields数据磁盘文件存储细节
Lucene 的数据域在内存中组织成Document和Field数据结构。每次建立索引的Document对象都可能拥有不同的Fields,而查询的时候,也可以通过查询词找到文档的相关Fields信息(这些Fields在创建的时候必须是Field.Store.YES )。这些Fields信息存储在后缀名为.fdx/.fdt/.fnm的磁盘文件中。
★ .fnm 域名存储文件
FieldInfos (.fnm) --> FNMVersion,FieldsCount, <FieldName, FieldBits> FieldsCount
FNMVersion --> VInt 域版本号(Lucene2.9新加的)
FieldsCount --> VInt 域的数量
FieldName --> String 域名
FieldBits --> Byte 域状态标志位
每个域的8位FieldBits记录了这个域的标志信息。我们从最低位开始,详细阐述一下这些标志位(其实大部分已经在《Document/Fields》阐述过了):
(1) No.1 bit:1表示此域被索引,0则不被索引。所谓被索引,也就是需要将这个域的值加入倒排索引表中去。
* Field.Index.NO则表示不被索引。
* Field.Index.ANALYZED则表示不但被索引,而且被分词。比如索引"hello world"后,无论是搜"hello",还是搜"world"都能够被搜到。
* Field.Index.NOT_ANALYZED表示虽然被索引,但是不分词。比如索引"hello world"后,仅当搜"hello world"时,能够搜到,搜"hello"和搜"world"都搜不到。
域还有一个标志来设定此域的值是否要被存储。
* Field.Store.Yes则表示存储此域,Field.Store.NO则表示不存储此域。
索引域(indexed)和存储域(Stored)的区别
* 建立索引的域可以将域值作为query来进行搜索。Lucene中建立索引的域如果不被存储,是不会将其域数据内容存放在.fdt文件中的,但其索引表会存放在另外的文件中。
* 只存储而不建立索引的域则不能通过键入query来搜索,但是可以通过其他Indexed域的搜索来得到相应的Stored域的信息。
很明显,这样区分是有意义的。在现实文档中的所有信息中,有这样一部分信息,是不太适合作为查询关键词,但是有必须被搜索出来的。比如文件路径,我们可以通过查询文件名来找到该文件的路径,但是谁也不会通过完整的文件路径去检索文件名吧?
(2) No.2 bit:1表示保存词向量,0为不保存词向量。
* Field.TermVector.YES表示保存词向量。
* Field.TermVector.NO表示不保存词向量。
(3) No.3 bit:1表示在词向量中保存位置信息。
* Field.TermVector.WITH_POSITIONS
(4) No.4 bit:1表示在词向量中保存偏移量信息。
* Field.TermVector.WITH_OFFSETS
(5) No.5 bit:1表示不保存标准化因子
* Field.Index.ANALYZED_NO_NORMS
* Field.Index.NOT_ANALYZED_NO_NORMS
(6) No.6 bit:标志是否保存Payload。
★ .fdx 域数据索引文件 .fdt 域数据文件
1、fdx 用于查找指定document的所有fields在.fdt文件中的位置,因为它包含的是固定长度的数据,因此这个文件可以很容易地进行随机访问。
FieldIndex (.fdx) --> DocFieldCount <FieldValuesPosition> SegSize
DocFieldCount --> UInt32 记录单个document存储的域的个数
FieldValuesPosition --> UInt64 记录当前document的域数据在.fdt文件中的偏移位置。
SegSize 指当前段中有多少个document
2、fdt 存放域的数据值。注意:fdt中只存放那些需要被存储(Stored)的域数据,而不被存储的(Stored)域是不存放在这里面的。下面有一个实例会详细解释这一点。
FieldIndex (.fdt) --> DocFieldCount <DocFieldData> SegSize
DocFieldData --> FieldCount, <FieldNum, Bits, Value> FieldCount
FieldCount --> VInt 当前document包含的域的个数
FieldNum --> VInt 当前域号
Bits --> Byte 记录域的标志位 (下面从低位开始)
(1) No.1 bit: 1 表示分词后的field , 0 表示 没有分词的field
(2) No.2 bit: 域包含二进制数据
(3) No.3 bit: 表示该 field 的压缩选项被开启 ,如果压缩选项开启,采用的压缩算法 是 ZLIB。
Value --> String 当前域的数据值
★ 专题用例 :
关于例子的详细信息参见《索引文件格式(2):文件结构总体框架 》最后的说明。
(1) 解释一下fnm文件中的数据
◆ 前五个字节(-2,-1,-1,-1,15,)表示fnm文件的版本号(粉红色区域)。上面我们提到了FNMVersion是VInt类型,而VInt类型是可变类型,我们怎么知道FNMVersion有5个字节呢。
在《索引文件格式(1):基础知识 》我们讲到了VInt类型每个Byte的最高位是不表示数值的,而表示后面是否还有一个字节。显然第1个byte=-2的最高位为1,后面有一个字节。知道第5个byte=15的最高位为0,因此后面没有字节了。则FNMVersion就用5个字节表示的,值为:(-2,-1,-1,-1,15,)
◆ 绿色区域的3表示Field的数量。该实例确实只有3个Fields:name、path、content。
◆ 浅蓝色部分就是这三个Fields的名字。拿其中一个来说明:(4,110,97,109,101,1)。在上面我们已经讲到了每个域名数据都有两部分构成<FieldName, FieldBits>。
先说说FieldName,它是一个String类型。在《索引文件格式(1):基础知识 》中我们讲到String由一个VInt来表示字符的数量,而后面的Chars分别用UTF-8编码每个字符。显然(4,110,97,109,101,)中的第一个4表示后面有4个字符,分别是110('n'),97('a'),109('m'),101('e')。
再说说FieldBits,在这里实例中最后一个字节byte=1表示这个数据。说明标志位的最低bit=1,而其他bit=0。因此说明"name"域是需要索引的域(Indexed)。
(2) 解释一下fdx文件中的数据
◆ 前4个字节(0,0,0,2)表示单个Document存储于多少个域,UInt32类型说明是固定4个字节大小。但是有个疑问了,在这个实例中,Document明明有3个Field,为什么这个值是2呢。原因就是第三个Field("content")的Stored is false。这个域是要分词并建立倒排索引的,但并不存储在.fdx和.fdt中。
◆ 后面每8个字节(UInt64类型)表示一个数据项。其数据值表示该Document的域数据值在.fdt文件中的存储位置。比如第一项(0,0,0,0,0,0,0,4)表示doc1中要存储的两个域(name和path)从.fdt数据中的第4个字节开始,到下一项(0,0,0,0,0,0,0,49)第49个字节为止。
(3) 解释一下fdt文件中的数据
◆ 前4个字节(0,0,0,2)和上面fdx文件一样。
◆ 后面正好有4个DocFieldData,记录了每个Document对象的name和path域的内容值。上面也讲到了Document有3个域,为什么content不存储进来呢。content是文档内容,远比name,path域的内容大的多。如果都想这样存储进fdt数据的话,这个文件将无比庞大。另外,content内容提供了查询倒排索引文件的关键字,我们在实际应用中,通过content中的词可以查询到包含此词的文档的path,也就能够进一步读取文档内容。没有必要再去存储一遍全部的原文档内容。这也就是为什么在建立索引的时候content域的Stored=false的原因了。