代码改变世界

Lucene 索引文件学习笔记

2012-11-25 10:03  chloe_zhou  阅读(920)  评论(2编辑  收藏

 序言

  Lucene的索引是放在一个文件夹中的,即这个文件夹中所有的文件构成Lucene的一个索引。Lucene的索引是分层的,依次为 Index(索引) --> Segment(段) --> Document(文档) --> Field(域) --> Term(词/词汇单元)。Lucene 使用不同的文件扩展名标识不同类型的索引文件,使用不同的文件名标识不同的 Segment,即具有相同的文件名前缀的文件属于同一个Segment 。例如,文件格式可以表示为_X.<ext>,其中X为段名,<ext>为扩展名,用来表示该文件对应索引的某个部分。

 正文

  为了对各个索引文件有一个概要性的理解,Lucene的索引文件夹中的文件可以用如下说明图来表示,

 

  可以看出,Lucene的索引包括所有segment的元数据文件"segments" 或 "segments_N", 一个"deletable"文件,和一系列的描述每个segment的文件集。另外,当IndexWriter在往索引中写内容时,会出现lock文件,索引更新完毕后lock文件消失。

  1. segments 文件

    segments文件也称为段文件,用于存放索引中所有segment(段)的元数据信息,它指向所有激活的段,一个索引中只包含一个这种文件。Lucene在读索引时,会先打开这个段文件,之后根据这个段文件中的内容打开它所指向的其他文件。一个索引可以包含多个段,段与段之间是独立的,添加新的document(文档)可以生成新的段,不同的段可以合并。在索引文件夹中,具有相同文件名前缀的的文件属于同一个段,具有同样的文件名前缀但文件扩展名不同的文件用来存储同一个段的不同类型的数据。索引中的"segments"文件没有扩展名,且在一个索引中可能包含多个"segments_N"文件,具有最大N的那个文件是活动的段文件。值N被称为"the generation",Lucene每次向索引提交更改时都会将这个N增加1。

    在这个段的元数据信息文件中,记录了索引的版本号(Version 即索引被修改的次数);索引中段的数量(SegCount);各个段的名称(SegName)、大小(SegSize 即此段中包含的document数量)等信息。

  2. lock 文件

    lock 文件即写锁文件,其文件名为"write.lock",默认存储在索引目录中。当有一个writer正在修改索引时,就会出现这个lock文件,以确保在一个时刻只有一个writer修改索引。

  3. deletable 文件

    在Lucene 2.1版本之前才有这个文件,它包含了那些需要被删除的文档的详细资料。在2.1版本以后,一个writer会动态地计算需要被删除的文件,从而就不需要此文件了。

  4. 每个segment包含的文件

    每个segment包含field集合信息、term集合信息、加权因子和所属段中被删除的文件信息,其中XXX.fnm、XXX.fdt和XXX.fdx为field集合信息;

      (1) XXX.fnm  

  域信息文件(FieldInfos)包含某个segment(名称为XXX)中所有field(域)的元数据信息,记录了该segment中域的数量(FieldsCount);各个域的名称(FieldName)、存储和索引方式的bit标记(FieldBits),其结构示意图如下所示。

    (2) XXX.fdt

  域数据文件(FieldData)保存某个段的所有存储域(stored field)的具体信息。如果段XXX中总共有SegSize篇文档,则XXX.fdt文件中总共有SegSize个项,每一项保存一篇文档的域信息。对于每一篇文档,有对应文档包含的域的数目(fieldcount)以及接下来的FieldCount个项,每一项保存一个域的信息。对于每一个域,其中有域编号(FieldNum)、8位的比特标记序列(Bits)以及域值(FieldValue)。其结构图如下所示。

    (3) XXX.fdx

  域索引文件(FieldIndex)用于在fdt中辨别每一个document的起始地址和终止地址。该文件也总共有SegSize个项,对应着XXX.fdt中的SegSize篇文档。这SegSize个项都是具有固定长度的UInt64整数,为对应的文档在fdt文件中的偏移量,由于是固定长度的信息,所以方便随机访问。XXX.fdx与XXX.fdt之间关系的示意图如下,

(4) XXX.prx

  词位置文件用于存储分词后各个词条在包含它的各个document中的位置,其示意图如下所示。

  此文件包含TermCount项,每一项对应一个分词结果term。对于每一个term都有一个大小为DocFreq的数组,DocFreq为包含该Term的文档数量,因此数组的每一项对应一篇包含该Term的文档。一个term可能在一篇文档中出现多次,这个次数便是Freq,因此对于每一篇包含该Term的文档,又有一个长度为Freq的数组,数组的每一项给出了Term出现一次的位置信息。其中每一个位置是采用"Delta"规则(差值规则,参考http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623597.html)存放。

(5) XXX.frq

   词倒排表文件存储了segment中每个Term的词频信息,即包含此term的文档编号列表以及文档中出现该term的次数。为了加快对倒排表的访问,此文件也包含了倒排表的跳表信息(关于跳表,请参考http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623597.html)。本文件的内容示意图如下所示。

 

  此文件包含TermCount项,每一项对应一个term,为该term的倒排表和跳跃表信息。在倒排表信息中,文档号采用"Delta"规则,如果term的omitTf(omit term frequency)设置为false,则不忽略term在某个document中出现的频率,即Freq信息保留,反之则无Freq信息,因此会有"Freq?"的表示,是说Freq这个词频信息可能出现也可能不出现。在跳跃表中,跳表的层数以及各层元素的个数由设置的跳跃间隔(SkipInterval)和包含某term的document数量(DocFreq)决定,例如第0层跳表包含DocFreq / SkipInterval 项,第k层跳表包含DocFreq / [SkipInterval ^ (k+1)] 项。跳表中不仅指明了某个文档在原倒排链表中的偏移量(位置)FreqSkip,也给出了某文档在XXX.prx文件中的对应的位置ProxSkip,从而能够从XXX.prx中提取具体的位置信息。FreqSkip和ProxSkip将frq文件中的跳表与倒排表联系起来,也将frq文件与prx文件联系起来,其联系图如下所示。

  图中,上面一块淡黄色区域表示文件XXX.frq,下面一块灰色区域表示文件XXX.prx ,由于此图是为了强调FreqSkip和ProxSkip的作用,所以将SkipDatum所包含的其他项省略了,且FreqSkip块用红色表示,ProxSkip块用绿色表示。假设SkipInterval=3,XXX.frq文件中某term的跳跃表的第0层有文档1、文档4、文档7、文档10、文档13、文档16,则这几个文档对应的SkipDatum中的FreqSkip分别指向本frq文件的倒排信息中对应文档的TermFreq;另外这几个文档对应的ProxSkip指向XXX.prx文件中对应的Positions信息,即term出现在这些文档中的位置信息。

  可见,frq中只有包含某term的文档编号列表,没有具体的文档内部位置信息。一个term出现在某个document中具体几次,分别出现在哪几个位置,这种信息需要从prx中获取

(6) XXX.tis

  XXX.tis是词信息文件(TermInfos),与之后将介绍的XXX.tii即词索引文件一起构成本segment的词典信息。词信息文件用于存储分词后的词条,即某个segment中所有Term数据,这些term先按照所属field的名称(按照UTF-16字符编码)排序,然后按照Term的字符串(按照UTF-16字符编码)排序。其详细内容如下图所示。

 

  为了加快对词的查找速度,在之后将介绍的tii文件中会对term进行类似跳跃表式地存放,因此在XXX.tis文件中通过"IndexInterval"来指定tii词索引信息中的索引间隔;其后的"SkipInterval"指定了前面frq文件中跳跃表的跳跃步数,"MaxSkipLevels"指定frq中跳跃表的最大跳跃层数,之后便是一系列的Term信息。其中每个"Term"项(Term文本数据)采用"Prefix+Suffix"规则(前缀后缀规则,参考http://www.cnblogs.com/forfuture1978/archive/2009/12/14/1623597.html) 存放,"FreqDelta"可以定位到本term在frq文件中对应的"TermPostingList"(倒排表)项,"ProxDelta"可以定位到本term在prx文件中对应的"TermPositions"项。可见 "FreqDelta"将XXX.tis 与 XXX.frq 联系起来,"ProxDelta"将XXX.tis 与 XXX.prx 联系起来。

(7) XXX.tii

  XXX.tii是XXX.tis的索引文件(TermInfo Index),用于加快对词典的查找速度,保存每隔IndexInterval个词。该文件在使用的时候会被全部加载到内存中,其中词的数量IndexTermCount = TermCount / IndexInterval 。其详细内容示意图如下所示。

 

  其中每个"TermIndice"项代表一个词的信息,包括此本身的信息"TermInfo"(与tis中含义一致)和该词在tis文件中对应的"TermInfo"块的位置,从而"IndexDelta"将 XXX.tii 与 XXX.tis 联系起来。

(8) XXX.nrm

  XXX.nrm文件也即加权因子文件(Normalization Factors)。在对查询结果进行排序之前,需要计算结果文档与搜索条件的相关性,在计算相关性的过程中需要计算某term相对于某document的重要性,即Term Weight ,这个Term Weight的计算公式中仅考虑了document中出现该term的次数以及term在所有文档中的普通程度,而没有考虑document长度、出现该term的field的重要性以及文档的重要性等因素,因此需要对原公式的计算结果进行调整,调整方法就是对之前的计算结果成上一个加权因子。

  对于一个term,它出现在不同的document或不同的field中时加权因子不同,因此加权因子的数量为文档数量乘以域的数量,这些加权因子都存在XXX.nrm文件中,具体结构示意图如下。

  标准化因子文件的头部为字符串"NRM"加上版本信息,之后是一个数组,每个数组元素对应一个参与打分的field。对应field的各个元素也是一个数组,长度为本segment中的document数量,每一个元素是一个字节,表示一个浮点数,低三位为尾数部分,高五位为指数部分。

(9) XXX.del

  .del文件用于标记segment中被删除的文档,且此文件只有当segment中存在被删除的文档时才会出现。在.del文件中,用一个bit标记一个文件,计数时既计bit的数量(BitCount)又记所占用的Byte数量(ByteCount).标记被删除文件的方法有两种,一种是用一个bit对应一个文件,当某bit置1时表示对应的文件被删除,涉及到的bit全部被存储,这种方法被称为Bits format(Bits 格式);另一种方法是对第一种方法表示的结果采用另一种方式存储,在删除文件数量较少的情况下可以减少存储空间,它只存储非0的Byte(字节)以及该Byte号(是第几个Byte),不存储为0的Byte,这种方法被称为DGaps format(DGaps 格式),具体机构示意图如下所示。

  其中,"[format]"指明了该.del文件用哪种格式标记被删除的文件,-1表示用DGaps格式,否则用Bits格式。假设第10、12、32号文件被删除,则第10位、12位和第32位比特被置1。如果采用Bits格式,则包含32个比特的5个字节都要被存在.del文件中;如果采用DGaps格式,则只用存5个字节中的不全为0的2个字节,具体描述如下。

  可见,对于例子中的情况,采用Bits格式会占用5个字节,而采用DGaps格式只用2个字节另加上两个整数所占的空间。