lucene索引结构(一)--segment元数据信息

今天起开始深入分析Lucene 3的索引结构。那就从最初步的段索引开始搞吧。
1. 建立索引
开始分析之前必须要创建索引,这里图省事啦,也没有去网上找什么文档集。直接拿lucene的doc来索引的,这是一堆html的文件,


这里只索引了文件的路径、最后修改时间和内容。
  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));

2.索引文件
经过索引之后得到的文件如下。



在建索引的程序中,必须要调用

writer.setUseCompoundFile( false);
否则只能得到segment文件和.cfs文件。
.cfs文件实际上是一个虚拟文件,它将.fdt,.fdx,.fnm,frq,.nrm,.prx,.tii,.tis等等文件打包成了一个单一文件。
因为随着系统的不断运行,索引文件会越变越多,需要同时打开多个文件句柄,在一些文件系统中会导致文件句柄不够用,而打包成复合文件后,可以缓解这种情况。
这是官方文档对.cfs文件的解释," An optional "virtual" file consisting of all the other index files for systems that frequently run out of file handles. "
为了方便分析,这里将默认生成复合文件关闭。
3.segments.gen文件分析
3.1 作用
要说清楚segments.gen的作用,就必须先说Lucene是怎么在多个segments_N中进行选择的。
随着索引的文件发生变化,比如删除索引等,会使得index中存在多个segments_N,一些older generation的索引暂时不能被删掉,例如正在提交的索引或是一些自定义策略如IndexDeletionPolicy的使用。而程序打开索引的时候,只能选择一个generation打开。
Lucene每次都会选最大N对应的segments索引的打开。
具体策略如下:
1) 在所有的segments_N中选择N最大的一个。逻辑见SegmentInfos.getCurrentSegmentGeneration(String[]  files),返回的gen记为genA
  public static long getCurrentSegmentGeneration( String[] files) {
    if (files == null) {
      return -1;
    }
    long max = -1;
    for (int i = 0; i < files. length; i++) {
      String file = files[i];
      if (file.startsWith(IndexFileNames. SEGMENTS) && !file.equals(IndexFileNames.SEGMENTS_GEN )) {
        long gen = generationFromSegmentsFileName(file);
        if (gen > max) {
          max = gen;
        }
      }
    }
    return max;
  }
2) 打开segments.gen,其中保存了当前的N值。其格式如下,读出版本号(Version),然后再读出两个N(见3.2),如果两者相等,则作genB 
如果genA和genB都是-1,抛异常;否则取它们最大的一个作为N。
try {
   int version = genInput.readInt();
   if (version == FORMAT_LOCKLESS) {
       long gen0 = genInput.readLong();
       long gen1 = genInput.readLong();
       message("fallback check: " + gen0 + "; " + gen1);
       if (gen0 == gen1) {
          // The file is consistent.
          genB = gen0;
          break;
       }
   }
} catch (IOException err2) {
      // will retry
} finally {
      genInput.close();
}

3)如果genA和genB都是-1,抛异常;否则取它们最大的一个作为N。

Lucene官方文档对segments.gen的描述是"This is used only as a fallback in case the current generation cannot be accurately determined by directory listing alone (as is the case for some NFS clients with time-based directory cache expiraation). "

好像很诡异的样子。至少我目前还不是很理解,其实。。我是一个屌丝,你懂的。

3.2 物理结构分析
segments.gen文件的物理结构包括一个
Int32位的version头,和两个Int64位的generation(即上面说的N)。
     如下图,

在UltraEdit中打开
1)  Int32 version header (FF FF FF FE , 即-2)
2)  Int64 generation recorded,   2次(00 00 00 00 00 00 00 02,即2),图中只框了一次。

可以看到Version头和代码里是一致的
public final class SegmentInfos extends Vector {
   ...
   public static final int FORMAT_LOCKLESS = -2;
   ...
}


    而generation为2,这个和segments_2可以对应上(应该绝大多数情况下,segments.gen的第一个Int32都能和segments_x中最大的那个x对应上,除非发生官方文档中描述的那种特殊情况)。

4. segments_N文件分析
4.1 作用
保存段索引的元数据。注意,只是元数据,实际真正的数据是保存在Field和Term中的。

4.2 物理结构分析
     Segments文件从开始到末尾,有如下字段,

     Format, Version, NameCounter, SegCount, NumField, IsCompoundFile, DeletionCount, HasProx, Diagnostics> SegCount, CommitUserData, Checksum 。

     见下图


各字段类型如下:

Format, NameCounter, SegCount, SegSize, NumField, DocStoreOffset, DeletionCount --> Int32

Version, DelGen, NormGen, Checksum --> Int64

SegName, DocStoreSegment --> String

Diagnostics --> Map

IsCompoundFile, HasSingleNormFile, DocStoreIsCompoundFile, HasProx --> Int8

CommitUserData --> Map

各字段的意义:

1) Format。索引文件版本,由于Lucene还在不断的开发,因此不同版本的Lucene有自己的特定版本号。例如Lucene 2.3此值是-3,而Lucene 2.9和Lucene 3.0.2这个值就是-9。
在源码里是由public static final int FORMAT_DIAGNOSTICS = -9;(public final class SegmentInfos)定义的。

2) Version。索引的版本号。索引创建时被设为 System. currentTimeMillis ();随后每次更新索引的时候都会自增。
Lucene可以通过这个字段来判断索引的版本以及是否被更新。

3) NameCounter。是下一个新段(Segment)的段名。所有属于同一个段的索引文件都以段名作为文件名,一般为_0.fdt,  _0.fdx,_0.frq 等。

4) SegCount表示段的个数。
这一多元组会重复SegCount次。本文中建立的索引文件中它为1。

5) SegName, 段名字,也是这个段所有其他文件的前缀,下图索引中的SegName是“_0”所以也可看到这个段的其他文件都是_0.xxx这样的。

6) SegSize, 这个段中索引的文件个数。在下图可以看到这个值是00 00 00 63,也即十进制的99。与实际相符,

7) DelGen, .del文件的版本号,如果是-1,表示没删除任何文档。(在Lucene中,被删除的文档不是立即物理删除,它们被保存在.del中,optimize的时候才会被物理清空)。这里建立的索引中DelGen为-1.

8)  DocStoreOffset,[ DocStoreSegment, DocStoreIsCompoundFile ]
对于域(Stored Field)和词向量(Term Vector)的存储可以有不同的方式,即可以每个段(Segment)单独存储自己的域和词向量信息,也可以多个段共享域和词向量,把它们存储到一个段中去。

如果DocStoreOffset为-1,则此段单独存储自己的域和词向量,从存储文件上来看,如果此段段名为XXX,则此段有自己的XXX.fdt,XXX.fdx,XXX.tvf,XXX.tvd,XXX.tvx文件。DocStoreSegment和DocStoreIsCompoundFile在此处不被保存。

如果DocStoreOffset不为-1,则DocStoreSegment保存了共享的段的名字,比如为YYY,DocStoreOffset则为此段的域及词向量信息在共享段中的偏移量。则此段没有自己的XXX.fdt,XXX.fdx,XXX.tvf,XXX.tvd,XXX.tvx文件,而是将信息存放在共享段的YYY.fdt,YYY.fdx,YYY.tvf,YYY.tvd,YYY.tvx文件中。

9)  HasSingleNormFile,
在搜索的过程中,标准化因子(Normalization Factor)会影响文档最后的评分,不同的文档重要性不同,不同的域重要性也不同。因而每个 文档的每个域都可以有自己的标准化因子。如果HasSingleNormFile为1,则所有的标准化因子都是存在.nrm 文件中的。如果HasSingleNo rmFile不是1,则每个域都有自己的标准化因子文件.fN。

10) NumField, NormGen数组的大小, 若为-1 则没有NormGens。

11) 如果每个域有自己的标准化因子文件,则此数组描述了每个标准化因子文件的版本号,也即.fN 的N。

12) IsCompoundFile, 表示是否使用了复合文件,即.cfs。这里为-1,和实际相符。

13) DeletionCount, 此段中删除的文档数目,本索引中为0.

14) HasProx,是否有词频信息。只要segment中的任一field的omitTf为false, 就取1;否则为0,表示此段中无任何词频信息。

15) Diagnostics, 调试信息。可以看到下图右侧那些什么java.version啊,Windows Vista什么的,就是那些调试信息啦。

16) CommitUserData, 保存了用户提供的从字符串到字符串的映射Map

17) Checksum, "contains the CRC32 checksum of all bytes in the segments_N file up until the checksum. "

用UltraEdit来打开Segments_2看到的,

上图中用不同颜色的框把不同字段区分开。不过后面的Diagnostics,CommitUserData和checksum就没框出来了。


括号里表示取值,可以用windows自带计算器做一下换算到十进制就很清楚啦。

1) Format  (-9)                       int 32   #Lucene 3.0.2对应的这个字段是-9,和代码吻合
2) version(1342860474916)      int64     #索引创建的时间戳
3) NameCounter(1)                int32     #有新端时,将会产生_1.fdx,_1.nrm这样的索引,都用"_1"做前缀
4) SegCount (1)                    int32      #只有一个段
5) SegName (_0)                    String,#可以看出和_0.fdx,_0.nrm这些文件的前缀(也就是文件名了)是一致的
6) SegSize(99)                      int32,#确实和被索引的文档数吻合
7) DelGen(-1)                       int64,#没删除任何文档的索引,所以是0
8) DocStoreOffset(-1)            int32
9) HasSingleNormFile (1)        int8
10) NumField(-1)                   int32
11) IsCompoundFile(-1)          int8 , #不是复合文件
12) DeletionCount (0)             int32,#没有删除过任何文档的索引
13) HasProx(1)                       int8
14)  Diagnostics                      String
15) CommitUserData               map
16) Checksum                        int64

5 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

你可能感兴趣的:(lucene)