Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4478066
  • 博文数量: 192
  • 博客积分: 10014
  • 博客等级: 上将
  • 技术积分: 8232
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-21 00:22
文章分类

全部博文(192)

文章存档

2011年(4)

2009年(14)

2008年(174)

我的朋友

分类:

2008-06-21 16:10:56

 
 

研究SegmentInfo类的实现。

虽然在阅读代码的时候,是一件很枯燥的事情,尤其是当代码非常地长,这使得我们感觉这是一种压力,但是如果把代码当成是一种乐趣的时候,你会发现代码是那样的富有感情色彩。呵呵。

SegmentInfo类在Lucene 2.0.0版本的时候,定义非常简单,就定义了一个构造函数,如下所示:

package org.apache.lucene.index;

import org.apache.lucene.store.Directory;

final class SegmentInfo {
public String name;      // unique name in dir
public int docCount;      // number of docs in seg
public Directory dir;      // where segment resides

public SegmentInfo(String name, int docCount, Directory dir) {
    this.name = name;
    this.docCount = docCount;
    this.dir = dir;
}
}

但是,到了2.2.0版本的时候,SegmentInfo类的定义就相当复杂了。

因为2.2.0版本是在之前的版本的基础上进行开发的,所以涉及到一些概念,尤其是文件格式,提前了解一下这些概念对于理解SegmentInfo类有很大帮助。

*************************************************************************************

Normalization Factors(标准化因子):可以参考

【在2.1之前的版本,存在下面的一些格式的文件】

在每个Document逻辑文件中,对于一个被索引的Field,都对应着一个norm文件,该norm文件的一个字节与Document有非常密切的关系。

这个.f[0-9]*文件,是为每个Document设置的,当Document中的一个Field被命中的时候,.f[0-9]*文件包含的就是:一个字节编码值乘以排序分值。

一个单独的norm文件的创建,仅仅是在一个复合segments文件被创建的时候才存在。即:一个单独的norm文件的存在是依赖于复合segments的,如果复合segments文件不存在,就不会生成一个单独的norm文件。

【在2.1版本及其之后的版本,存在下面的一些格式的文件】

只使用一个.nrm文件,该文件包含了所有的标准化因子。所有的.nrm文件都包含NormsHeader和 NumFieldsWithNorms 这两部分。

其中:

NormsHeader包含4个字节,前三个字节为“N”、“R”、“M”,最后一个字节指定了FORMAT版本号,当前因为是2.2.0版本,版本号为-1,可以在SegmentInfos类的定义开始部分看到:

public static final int FORMAT = -1;

对应这一个,而NumFieldsWithNorms 对应于一个Segments的大小,即SegSize

一个.nrm文件的每个字节都编码成一个浮点值,该字节的前3bits(即0-2)表示尾数,后5bits(即3-7)表示指数。

当一个已经存在segments文件的标准化因子值(即norm的value)被修改,同时一个单独的norm文件被创建;当Field N被修改,同时一个.sN文件被创建,这个.sN文件用来保存该Field的标准化因子值(即norm的value)。

当适当的时候,一个单独的norm文件的创建,可以是为了一个复合segments文件,也可以不是复合segments文件。

*************************************************************************************

在2.2.0版本中,SegmentInfo类实现的源代码如下所示:

package org.apache.lucene.index;

mport org.apache.lucene.store.Directory;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.store.IndexInput;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;

final class SegmentInfo {

static final int NO = -1;          // 如果不存在标准化因子.nrm文件,和.del文件
static final int YES = 1;          // 如果存在标准化因子.nrm文件,和.del文件
static final int CHECK_DIR = 0;    // 需要检查该索引目录,是否存在标准化因子.nrm文件,和.del文件
static final int WITHOUT_GEN = 0; // 一个索引文件的名称中,没有gen值,即:根本没有对其进行过添加(或者是追加、删除)操作

public String name;      // 在索引目录中的唯一索引文件名
public int docCount;      // 在索引段中Document的数量
public Directory dir;      // 一个索引段文件所属于的索引目录为dir

private boolean preLockless;                    // 为true,当一个索引段文件在无锁提交之前被写入

private long delGen;                            // 当前版本(generation)删除时的gen的值为delGen(即segments_delGen.del)
   
private long[] normGen;                         //   每个Field对应的norm文件的gen                                                

private byte isCompoundFile;                    // 如果为NO,则表示不用检查;如果为YES表示检查是否存在2.1版本之前的扩展名为.cfs和.nrm的文件        

private boolean hasSingleNormFile;              //如果一个segments存在一个单独的norm文件。在当前版本中,false表示一个segments是由DocumentWriter生成的,true表示为最新创建的合并的索引段文件(包括复合segments文件和非复合segments文件)

private List files;                            // 在当前索引目录中,当前索引段用到的文件在缓存中的列表

// 构造一个SegmentInfo对象(根据指定的参数:索引段名称、索引文件中Document的数量、指定的所以目录)

public SegmentInfo(String name, int docCount, Directory dir) {
    this.name = name;
    this.docCount = docCount;
    this.dir = dir;
    delGen = NO;    // 初始化一个SegmentInfo的时候,指定存在标准化因子.nrm文件,和.del文件
    isCompoundFile = CHECK_DIR;    // 检查该索引目录dir,看是否存在一些2.1版本之前的扩展名为.cfs和.nrm的文件
    preLockless = true;    // 为true,当一个索引段文件在无锁提交之前被写入
    hasSingleNormFile = false;    // 指定一个segments是由DocumentWriter生成的
}

// 构造一个SegmentInfo对象

public SegmentInfo(String name, int docCount, Directory dir, boolean isCompoundFile, boolean hasSingleNormFile) {
    this(name, docCount, dir);
    this.isCompoundFile = (byte) (isCompoundFile ? YES : NO);
    this.hasSingleNormFile = hasSingleNormFile;
    preLockless = false;    // 为false,当一个索引段文件不是在无锁提交之前被写入
}

/**
   * 根据指定的SegvmentInfo,拷贝它到我们当前的SegmentInfo实例中,其实就是重新构造一个
SegmentInfo,重新构造的该SegmentInfo与指定的SegmentInfo src是相同的
   */
void reset(SegmentInfo src) {
    files = null;
    name = src.name;
    docCount = src.docCount;
    dir = src.dir;
    preLockless = src.preLockless;
    delGen = src.delGen;
    if (src.normGen == null) {    // 每个Field对应的norm文件的gen不存在(为null)
      normGen = null;
    } else {
      normGen = new long[src.normGen.length];

//      arraycopy的声明为:public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
      System.arraycopy(src.normGen, 0, normGen, 0, src.normGen.length);
    }
    isCompoundFile = src.isCompoundFile;
    hasSingleNormFile = src.hasSingleNormFile;
}

// 构造一个SegmentInfo对象,通过构造一个索引输入流对象IndexInput

SegmentInfo(Directory dir, int format, IndexInput input) throws IOException {
    this.dir = dir;
    name = input.readString();
    docCount = input.readInt();
    if (format <= SegmentInfos.FORMAT_LOCKLESS) {
      delGen = input.readLong();
      if (format <= SegmentInfos.FORMAT_SINGLE_NORM_FILE) {
        hasSingleNormFile = (1 == input.readByte());
      } else {
        hasSingleNormFile = false;
      }
      int numNormGen = input.readInt();
      if (numNormGen == NO) {
        normGen = null;
      } else {
        normGen = new long[numNormGen];
        for(int j=0;j          normGen[j] = input.readLong();
        }
      }
      isCompoundFile = input.readByte();
      preLockless = (isCompoundFile == CHECK_DIR);
    } else {
      delGen = CHECK_DIR;    // CHECK_DIR = 0
      normGen = null;
      isCompoundFile = CHECK_DIR;   
      preLockless = true;    // 一个索引段文件在无锁提交之前被写入过
      hasSingleNormFile = false;    // 一个segments是由DocumentWriter生成的
    }
}

// 为每个Field的norm文件设置gen值

void setNumFields(int numFields) {
    if (normGen == null) {
      // 如果是2.1版本之前的segment文件,或者该segment文件没有对应的norm文件,则normGen=null
      normGen = new long[numFields];

      if (preLockless) {
        // 保持normGen[k]==CHECK_DIR (==0), 之后会为这些norm文件检查文件系统,因为它们是以前版本中无锁提交的
        
      } else {
        // 这是一个无锁提交写入的segment, 不存在单独的norm文件
        for(int i=0;i          normGen[i] = NO;    // 对每个Field,设置norm文件的gen值为NO=-1
        }
      }
    }
}

// 是否存在.del文件

boolean hasDeletions()
    throws IOException {
    //   如果delGen == NO==-1: 表示使用LOCKLESS模式,其中一些segment不存在.del文件
    if (delGen == NO) {
      return false;
    } else if (delGen >= YES) {   // YES的值为1,如果segment文件是无锁提交的,其中一些存在.del文件
      return true;
    } else {
      return dir.fileExists(getDelFileName());   // getDelFileName()在后面定义了该方法,获取删除的该索引目录下的文件名
    }
}

void advanceDelGen() {
    // delGen 0 is reserved for pre-LOCKLESS format
    if (delGen == NO) {
      delGen = YES;
    } else {
      delGen++;
    }
    files = null;
}

// 设置delGen的值为NO,即-1

void clearDelGen() {
    delGen = NO;
    files = null;
}

public Object clone () {    // SegmentInfo支持克隆
    SegmentInfo si = new SegmentInfo(name, docCount, dir);
    si.isCompoundFile = isCompoundFile;
    si.delGen = delGen;
    si.preLockless = preLockless;
    si.hasSingleNormFile = hasSingleNormFile;
    if (normGen != null) {
      si.normGen = (long[]) normGen.clone();
    }
    return si;
}

String getDelFileName() {    获取删除的该索引目录下的文件名
    if (delGen == NO) {
      // 如果delGen==NO,则表示不删除
      return null;
    } else {
      // 如果delGen是CHECK_DIR,,则它是以前的无锁提交格式
      return IndexFileNames.fileNameFromGeneration(name, "." +IndexFileNames.DELETES_EXTENSION, delGen);     // 即返回文件名为name_delGen.del,例如segments_5.del
    }
}

/**
   * 如果该索引段的这个Field作为separate norms文件(__N.sX)进行存储
   * fieldNumber是一个需要检查的field的索引
   */

boolean hasSeparateNorms(int fieldNumber)
    throws IOException {
    if ((normGen == null && preLockless) || (normGen != null && normGen[fieldNumber] == CHECK_DIR)) {
      // Must fallback to directory file exists check:
      String fileName = name + ".s" + fieldNumber;
      return dir.fileExists(fileName);
    } else if (normGen == null || normGen[fieldNumber] == NO) {
      return false;
    } else {
      return true;
    }
}

/**
   * 返回true如果这个segments中的任何一个Field都存在一个单独的norm文件
   */

boolean hasSeparateNorms()
    throws IOException {
    if (normGen == null) { // 如果normGen = null
      if (!preLockless) {
        // 不存在norm文件,返回false
        return false;
      } else {
        // segment使用pre-LOCKLESS模式保存,需要回退到最初的目录下,进行核查
        String[] result = dir.list();
        if (result == null)    //   如果获取的文件列表为null
          throw new IOException("cannot read directory " + dir + ": list() returned null");
        // 否则,如果获取的文件列表不空
        String pattern;
        pattern = name + ".s";    // 设置文件名的匹配格式字符串,形如name.s的形式
        int patternLength = pattern.length();
        for(int i = 0; i < result.length; i++){    //   循环匹配
          if(result[i].startsWith(pattern) && Character.isDigit(result[i].charAt(patternLength)))
            return true;
        }
        return false;
      }
    } else {    // 如果normGen != null
      // 这个segment使用LOCKLESS模式保存
      // 需要检查是否任何一个normGen都是 >= 1的
      // (它们有一个单独的norm文件):

      for(int i=0;i        if (normGen[i] >= YES) {    //   YES=1
          return true;
        }
      }
      // 查找normGen == 0的,这些情况是re
      // pre-LOCKLESS模式提交的,需要检查:

      for(int i=0;i        if (normGen[i] == CHECK_DIR) {
          if (hasSeparateNorms(i)) {
            return true;
          }
        }
      }
    }

    return false;
}

/**
   * 为每个Field的norm文件的gen,执行加1操作
   * @param fieldIndex:指定的Field的norm文件需要被重写,fieldIndex即对应的norm文件的gen值
   */

void advanceNormGen(int fieldIndex) {
    if (normGen[fieldIndex] == NO) {
      normGen[fieldIndex] = YES;
    } else {
      normGen[fieldIndex]++;
    }
    files = null;
}

/**
   * 获取Field的norm文件的文件名称;number是一个Field的索引值
   */

String getNormFileName(int number) throws IOException {
    String prefix;

    long gen;
    if (normGen == null) {
      gen = CHECK_DIR;    // CHECK_DIR==0
    } else {
      gen = normGen[number];    //   根据Field的索引值获取它对应的norm文件的gen值,然后使用该gen值取得索引段文件的文件名
    }
   
    if (hasSeparateNorms(number)) {
      // case 1: separate norm
      prefix = ".s";
      return IndexFileNames.fileNameFromGeneration(name, prefix + number, gen);     //   使用gen值取得索引段文件的文件名,如果name=“segments”,number=7,gen=4,则返回的文件名为segments_4.s7
    }

    if (hasSingleNormFile) {     //   如果存在一个单独的norm文件
      // case 2: lockless (or nrm file exists) - single file for all norms
      prefix = "." + IndexFileNames.NORMS_EXTENSION;    // IndexFileNames.NORMS_EXTENSION=nrm
      return IndexFileNames.fileNameFromGeneration(name, prefix, WITHOUT_GEN);   //   如果name=“segments”,则返回的文件名为segments.nrm
    }
     
    // case 3: norm file for each field
    prefix = ".f";
    return IndexFileNames.fileNameFromGeneration(name, prefix + number, WITHOUT_GEN); //   如果name=“segments”,number=7,则返回的文件名为segments.f7
}

/**
   *指定,是否segment文件作为复合文件存储()
   */
void setUseCompoundFile(boolean isCompoundFile) {
    if (isCompoundFile) {
      this.isCompoundFile = YES;
    } else {
      this.isCompoundFile = NO;
    }
    files = null;
}

/**
   * 如果索引文件被作为复合文件存储,则返回true
   */

boolean getUseCompoundFile() throws IOException {
    if (isCompoundFile == NO) {
      return false;
    } else if (isCompoundFile == YES) {
      return true;
    } else {
      return dir.fileExists(name + "." + IndexFileNames.COMPOUND_FILE_EXTENSION);
    }
}

/**
   *保存segment的信息,其实就是输出(写入)到磁盘中的索引目录中
   */

void write(IndexOutput output)
    throws IOException {
    output.writeString(name);
    output.writeInt(docCount);
    output.writeLong(delGen);
    output.writeByte((byte) (hasSingleNormFile ? 1:0));
    if (normGen == null) {
      output.writeInt(NO);
    } else {
      output.writeInt(normGen.length);
      for(int j = 0; j < normGen.length; j++) {
        output.writeLong(normGen[j]);
      }
    }
    output.writeByte(isCompoundFile);
}

/*
   * 返回所有的被当前SegmentInfo引用的所有的文件的列表,由于是在本地缓存中,你不应该设法去修改他们
   */

public List files() throws IOException {

    if (files != null) {
      return files;
    }
   
    files = new ArrayList();
   
    boolean useCompoundFile = getUseCompoundFile();

    if (useCompoundFile) {
      files.add(name + "." + IndexFileNames.COMPOUND_FILE_EXTENSION);    // IndexFileNames.COMPOUND_FILE_EXTENSION=csf
    } else {

/* INDEX_EXTENSIONS_IN_COMPOUND_FILE定义了如下所示的复合文件扩展名:

static final String[] INDEX_EXTENSIONS_IN_COMPOUND_FILE = new String[] {
      "fnm", "fdx", "fdt", "tii", "tis", "frq", "prx",
      "tvx", "tvd", "tvf", "nrm"
};

*/
      for (int i = 0; i < IndexFileNames.INDEX_EXTENSIONS_IN_COMPOUND_FILE.length; i++) {
        String ext = IndexFileNames.INDEX_EXTENSIONS_IN_COMPOUND_FILE[i];
        String fileName = name + "." + ext;
        if (dir.fileExists(fileName)) {
          files.add(fileName);
        }
      }
    }

    String delFileName = IndexFileNames.fileNameFromGeneration(name, "." + IndexFileNames.DELETES_EXTENSION, delGen);    //   IndexFileNames.DELETES_EXTENSION=del;如果name=“segments”,delGen=6,则返回的文件名为segments_6.del  
    if (delFileName != null && (delGen >= YES || dir.fileExists(delFileName))) {
      files.add(delFileName);
    }

    // Careful logic for norms files   
    if (normGen != null) {
      for(int i=0;i        long gen = normGen[i];
        if (gen >= YES) {
          // Definitely a separate norm file, with generation:
          files.add(IndexFileNames.fileNameFromGeneration(name, "." + IndexFileNames.SEPARATE_NORMS_EXTENSION + i, gen));    //  IndexFileNames.SEPARATE_NORMS_EXTENSION=s;如果name=“segments”,gen=6,i=8,则返回的文件名为segments_6.s8  
        } else if (NO == gen) {
          // No separate norms but maybe plain norms
          // in the non compound file case:
          if (!hasSingleNormFile && !useCompoundFile) {
            String fileName = name + "." + IndexFileNames.PLAIN_NORMS_EXTENSION + i;    //  IndexFileNames.PLAIN_NORMS_EXTENSION=f;如果name=“segments”,gen=6,i=8,则返回的文件名为segments_6.f8
            if (dir.fileExists(fileName)) {
              files.add(fileName);
            }
          }
        } else if (CHECK_DIR == gen) {
          // 2.1版本之前:我们需要验证这些文件的存在性
          String fileName = null;
          if (useCompoundFile) {
            fileName = name + "." + IndexFileNames.SEPARATE_NORMS_EXTENSION + i;    // 若name="segments",i=X=8,则该类文件形如segments.s8
          } else if (!hasSingleNormFile) {
            fileName = name + "." + IndexFileNames.PLAIN_NORMS_EXTENSION + i;    // 若name="segments",i=X=8,则该类文件形如segments.f8
          }
          if (fileName != null && dir.fileExists(fileName)) {
            files.add(fileName);
          }
        }
      }
    } else if (preLockless || (!hasSingleNormFile && !useCompoundFile)) {
      // 2.1版本之前的: 需要为我们当前的索引段扫描索引目录找到所有的匹配的_X.sN或_X.fN的文件
      String prefix;
      if (useCompoundFile)
        prefix = name + "." + IndexFileNames.SEPARATE_NORMS_EXTENSION;    // 若name="segments",则该类文件形如segments.s
      else
        prefix = name + "." + IndexFileNames.PLAIN_NORMS_EXTENSION;    // 若name="segments",则该类文件形如segments.f
      int prefixLength = prefix.length();
      String[] allFiles = dir.list();
      if (allFiles == null)
        throw new IOException("cannot read directory " + dir + ": list() returned null");
      for(int i=0;i        String fileName = allFiles[i];
        if (fileName.length() > prefixLength && Character.isDigit(fileName.charAt(prefixLength)) && fileName.startsWith(prefix)) {
          files.add(fileName);
        }
      }
    }
    return files;
}
}

通过SegmentInfo类的定义,总结一下:

1、主要针对2.1版本前后的不同形式的Segments进行处理,尤其是复合segments文件;

2、对每个Field的norm文件进行处理:设置该norm文件的gen;

3、使用write()方法,将一个处理过的Segments文件写入索引目录;

4、2.1版本以后,统一使用一个.nrm文件,该文件包含了所有的标准化因子,因为需要对2.1版本以前的版本进行支持,需要处理2.1版本之前的一些版本中,对标准化因子设置文件进行处理。

5、根据getNormFileName(int number)方法,可以总结出:

(1) 通过一个索引段文件的索引值number,可以得到它的norm文件的gen值,这个gen值其实就是这个segments的gen,即形如segments_N形式,N=gen。

(2) 2.1版本以后的,norm文件只使用一个.nrm扩展名的文件来代替以前的norm文件,则这个文件就是segments.nrm文件,并且对于同一个索引段,只有一个segments.nrm文件。

(3) 2.1版本以前的,norm文件的形式有:segments.fN、segments_N.sX两种。其中N是gen值,X是一个Field在Document中的索引值。

6、通过调用files()方法,可以获取索引目录下存在的不同扩展名的文件的一个列表,即该索引目录下的所有可能存在文件(通过文件扩展名区分),可以分出下面几组:

(1) 如果useCompoundFile=true,获取到扩展名为.cfs的复合文件;

(2) 如果useCompoundFile=false,获取到扩展名为如下的复合文件:

fnm、fdx、fdt、tii、tis、frq、prx、tvx、tvd、tvf、nrm;

(3) 如果是删除文件,获取到扩展名为.del的文件,若name="segments",delGen=6,则文件名形如segments_6.del;

(4) 如果normGen != null且normGen[i] >= YES=1,获取到扩展名为.sX的文件,若name="segments",gen=6,i=X=8,则该类文件形如segments_6.s8;

(5) 如果normGen != null且normGen[i] = NO=-1且hasSingleNormFile=false且useCompoundFile=false,获取到扩展名为.fX的文件,若name="segments",gen=6,i=X=8,则该类文件形如segments_6.f8;

(6) 如果normGen != null且normGen[i] = CHECK_DIR=0且useCompoundFile=true,获取到扩展名为.sX的文件,若name="segments",i=X=8,则该类文件形如segments.s8;

(7) 如果normGen != null且normGen[i] = CHECK_DIR=0且hasSingleNormFile=false且useCompoundFile=false,获取到扩展名为.fX的文件,若name="segments",i=X=8,则该类文件形如segments.f8;

(8) 如果normGen == null的时候,preLockless = true或(||)(hasSingleNormFile=false且useCompoundFile=false),这时若useCompoundFile=true,则获取到扩展名为.s的文件,若name="segments",则该类文件形如segments.s;

(9) 如果normGen == null的时候,preLockless = true或(||)(hasSingleNormFile=false且useCompoundFile=false),这时若useCompoundFile=false,则获取到扩展名为.f的文件,若name="segments",则该类文件形如segments.f;

阅读(806) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~