您的位置:首页 > 其它

lucene索引结构(二)--域(Field)信息索引

2012-07-22 19:46 211 查看
1. 域(Field)的元数据信息(.fnm)文件分析
1.1 作用
我们在为文档建立索引的时候,会为文档添加不同的域(字段)来进行索引,使得索引结构能满足更多的查询语法。例如一个文档集被索引了author,modifydate字段,那么就能支持 'author:wangzhengnb AND modifydate>20120722' 这种Query语法。
更真实的例子就是搜索引擎普遍支持的site语法,也就是对网页索引时添加了site域,这样用户输入'意甲 site:sports.sina.com.cn' 时,搜索后端就能根据网页的site field来判断这个网页是不是新浪体育的,从而实现针对新浪体育频道域下的搜索。如下图,



而.fnm文件就是索引了这些域的元信息。比如一共有多少个域,某个域是否被存储、是否保存位置信息、是否保存偏移等等。

1.2 .fnm的物理结构分析
.fnm的物理结构如下图,



.fnm文件分为FNMVersion,FieldsCount, <FieldName, FieldBits>FieldsCount

FNMVersion, FieldsCount --> VInt

FieldName --> String

FieldBits --> Byte

FNMVersion对应版本号,对应Lucene-3.0.2为-2。

FieldsCount表示这个段中Field的总数。

<FieldName, FieldBits>元组是一个Field的信息,它一共重复FieldsCount次;其中的FieldName是Field名称,FieldBits表示这个域的属性,具体如下表。

1) 最低位如果为0,表示这个域不被索引(Field.Index.NO);为1,表示被索引(Field.Index.ANALYZED不仅索引而且分词,Field.Index.NOT_ANALYZED仅索引不分词),加入倒排表。
2) 次低位为0,表示不保存词向量(TermVector)(Field.TermVector.NO),为1表示保存词向量(Field.TermVector.YES)
3) 倒数第三位如果为1表示在词向量中保存位置信息(Field.TermVector.WITH_POSITIONS)
4) 倒数第四位如果为1表示在词向量中保存偏移量信息(Field.TermVector.WITH_OFFSETS)
5) 倒数第五位如果为1表示不保存偏移量因子(Field.Index.NOT_ANALYZED_NO_NORMS)
6) 倒数第六位如果为1表示保存Payload信息。
Notes:
1) 索引域(Indexed)和存储域(Stored)的区别

◦ 一个域为什么会被存储(store)而不被索引(Index)呢?在一个文档中的所有信息中,有这样一部分信息,可能不想被索引从而可以搜索到,但是当这个文档由于其他的信息被搜索到时,可以同其他信息一同返回。

◦ 举个例子,读研究生时,您好不容易写了一篇论文交给您的导师,您的导师却要他所第一作者而您做第二作者,然而您导师不想别人在论文系统中搜索您的名字时找到这篇论文,于是在论文系统中,把第二作者这个Field的Indexed设为false,这样别人搜索您的名字,永远不知道您写过这篇论文,只有在别人搜索您导师的名字从而找到您的文章时,在一个角落表述着第二作者是您分信息,可能不想被索引从而可以搜索到,但是当这个文档于其他的信息被搜索到时,可以同其他信息一同返回。

2) payload的使用

◦ 我们知道,索引是以倒排表形式存储的,对于每一个词,都保存了包含这个词的一个链表,当然为了加快查询速度,此链表多用跳跃表进行存储。

◦ Payload信息就是存储在倒排表中的,同文档号一起存放,多用于存储与每篇文档相关的一些信息。当然这部分信息也可以存储域里(stored Field),两者从功能上基本是一样的,然而当要存储的信息很多的时候,存放在倒排表里,利用跳跃表,有利于大大提高搜索速度。



利用Payload,我们可以为文档附加上一些自定义的信息。比如为文档增加一个自定义的Term "$$$_Internal_ID"以及声明特殊的Field "$$$_Internal_ID",用以为文档保存一份不同于Lucene生成的ID。这样在倒排里就可以有一个"$$$_Internal_ID"的词项指向有这些域的文档的倒排,从而查找这些文档的自定义的ID。

1.3 深入分析.fnm文件
用UE打开_0.fnm文件,下图是我用不同颜色标注的各个字段的信息。



1) FNMVersion, VInt FEFFFFFF0F, decode出来就是4294967294(126 + 127*128 + 127*128^2 + 127*128^3 + 15*128^4), 也就是-2了。

这和官方文档上叙述的"FNMVersion (added in 2.9) is always -2."是一致的。

不过让我略觉蛋疼的是官方文档上也清楚的写着"VInt - A variable-length format for positive integers ",尼玛这不是正数吗。靠int32溢出来转成-2。。。而且不就 是一个版本号么,搞个定长的int32数就完了呗,反正也就存一次,能耗多少空间嘛。。真是的,蛋疼。。

2) FieldsCount, 也是Vint, 0x03, 解码后就是3,和建索引的代码是一致的。一共加入了'path','modified'和'contents'三个域。

doc.add(new Field( "path", f.getPath(), Field.Store.YES, Field.Index.NOT_ANALYZED ));
doc.add(new Field("modified",DateTools. timeToString(f.lastModified(), DateTools.Resolution.MINUTE ),Field.Store. YES,Field.Index. NOT_ANALYZED));
BufferedReader br = new BufferedReader(read);
doc.add(new Field("contents", br));


[align=left]后面跟着的就是FieldsCount个域。[/align]

[align=left] 3) 这里只展示第一个域。首先是FieldName,String类型,String也就是一个VInt表示长度len,后面跟着len个Byte。[/align]

[align=left] 这里的长度是04,解码也就是4。后面读出的4个Byte就是这个域的名字,也就是'path'了,从右侧也可以看到。[/align]

[align=left] 这之后跟着的就是下一个域,长度为08(也就是8),名字是'modified'。然后又是下一个。。[/align]

[align=left][/align]

2. 域数据文件(.fdx和.fdt)分析
2.1 作用

前面提到的域元数据文件,是从一个整体上来对段中的域进行描述。

然而当你想查找某篇文档都有哪些Field,这篇文档的这些个Field都取些什么值,都有啥属性,那就得依靠域数据文件了。

PS:从上句话也可以看出来,这个域数据索引,也是一个典型的正向索引(文档ID--->文档域信息)。

域数据文件分成域数据索引(Field data index, .fdx)和域数据(Field data, .fdt)两部分,下面将对它们介绍。

2.2 域数据索引及域数据的物理结构分析

.fdx和.fdt文件的物理结构如下图所示:



坑爹的地方来了。这幅图原来在网上找到的时候是没有前面粉红色的Format Version字段的,连和代码一起下下来的官方文档里都没提这俩字段。

尼玛啊,这索引文件可是二进制的啊,用UE打开全是00 01 FF的这种有木有,怎么看都怎么不对,遂上网搜以及跟代码看。终于发现是Lucene2.9之后再.fdx和.fdt最前面加入了一个format version字段。。。可参见/article/8863362.html

上述索引和数据的关系大体如下:
段内检索了多少个文档,在.fdx中就有多少个FieldValuePosition,这是一个UInt64的定长数据,指向的是.fdt的一个绝对偏移地址。这个地址上保存的就是这篇文档的域信息。

显而易见,.fdx因为是一个由定长数据组成的记录,所以docID=n的文档的域数据地址,就被保存在索引文件.fdx的4+n*8的位置上,然后读出绝对偏移后,就可以读取.fdt的这个偏移上的数据,就是docID=n文档的域信息了。

一篇文档的域信息包括文档域的个数,域编号(文档内的编号,从0开始),域属性,域的值。

详细的解释如下:

域数据索引:
1) Format Version: Int32数据,Lucene-3.0.2的这个值对应的是2。
2) 之后就跟的是SegSize个(段的大小,也即文档总数,在segment索引可找到它的身影,见/article/10579241.html) FieldValuesPosition,都是UInt64数据。表示.fdt文件中的绝对偏移。

域数据:
1) Format Version: Int32数据,Lucene-3.0.2的这个值对应的是2。

2) 之后跟着SegSize个DocFieldData。DocFieldData可不是定长数据,因为每个文档对应的某个域的取值都是不同的,文档也不都一定有相同数量的域等等。

3) DocField由FieldCount开头,是个Vint变长数据,标志这个文档一共有多少个域。
接下来就是1个Bits段,占1字节,解释见下表。

最低位为1表示这个域是分词的(tokenized)
次低位为1表示这个域是二进制数据,否则是文本,也即字符串数据

倒数第三位为1表示这个域采用了压缩算法(ZLib)
最后跟着的是Value段,Value可根据域的取值是二进制还是文本,或为BinaryValue类型或为String类型。
这里只讨论String类型的。同样String还是由VInt和Char bytes组成。

2.3 深入分析.fdx和.fdt文件

UE打开它俩,截图如下,因为文件很长,也没必要全部截完图,所以只截了开头的一部分。并把它俩贴到一副图里,方便叙述。



嗯哈?有点乱?不要怕,接下来一步步看,很简单。
首先看看.fdx文件:
1)由一个Format Version开头,占Int32空间,取值为0x02,也即2。

2) 紧跟着的是99个文档的域信息在.fdt中的绝对偏移。这里框取了前3个,它们的偏移分别是0x04,0x3F,0x7C。
看看下方的.fdt文件,3个红色的圈圈圈住的地方,就是这三个文档的域信息的开头的地方。

在看看.fdt文件:
1) 同样由一个Format Version开头,占Int32空间,取值为0x02,也即2。

2)接下来的是文档的域总数FieldCount,为2。等等!不是建立了path,modified和contents三个域嘛?!怎么只有俩!别着急,原来是contents只索引,不保存。如果每篇文档都保存全部的正文部分,那空间开销好大哦。

3) 接着的是这个域在文档中的编号,第一个绿圈里的数是00,第二个绿圈对应的是01,也即0号域和1号域。注意,这个编号是文档内的。到了下一篇文档,域编号又会从0开始啦。

4) 接下来是Bits了,取0。也即这个域不分词、为字符串类型、不压缩,这个和实际的索引建立程序是吻合的。

5) 之后是Value了,这里我把String类型拆成了Vint(紫色)和char bytes(棕色)来画,方便看些:)

可看到文档0的域0的value长度为0x28,也就是40。所以从接下来的0x08到0x2F就是文档0的域0的值,可以从右侧看到是它的路径,E:\lucene\....什么的。

如是分析文档0的域1的信息,都能和实际吻合。当域1的value在0x3E结束后,又开始了文档1的域信息,这印证了.fdx中文档1的偏移地址是0x3F的正确性。

3 Reference
[1] Apache Lucene - Index File Formats
http://lucene.apache.org/core/old_versioned_docs/versions/2_9_0/fileformats.html#File Naming

[2] forfuture1978的专栏

http://blog.csdn.net/forfuture1978

[3] a276202460的专栏--边学边记(七) lucene索引结构四(_N.fdx,_N.fdt)
/article/8863362.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: