Lucene源代码之SegmentInfo

 首先还是来看看,SegmentInfo到底是干什么的吧:

Segmentinfo是保存在SegmentInfos容器里面,它主要是记录每个segment的情况。还要引用前面的Segments_N文件格式图:

 

看到里面两个大的虚线框了吗,每一个虚线框就表示一个segment,里面的信息就是SegmentInfo需要处理(保存、写入)的数据,也就是它的属性,具体有SegName、SegSize、DelGen、HasSingleNormFile、NumField、NormGen[]数组以及IsCompoundFile。

SegmentInfo类所要做的事情有:

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

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

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

4,获取索引目录下存在的不同扩展名的文件,可以分出下面几组:

(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] >0,获取到扩展名为.sX的文件,若name="segments",gen=6,i=X=8,则该类文件形如segments_6.s8;

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

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

(7) 如果normGen != null且normGen[i] =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;

详细内容看下面剖析SegmentInfo源码:

  1. package org.apache.lucene.index;
  2. import org.apache.lucene.store.Directory;
  3. import org.apache.lucene.store.IndexOutput;
  4. import org.apache.lucene.store.IndexInput;
  5. import java.io.IOException;
  6. final class SegmentInfo {
  7.   public String name;                 // unique name in dir
  8.   public int docCount;                // number of docs in seg
  9.   public Directory dir;               // where segment resides
  10.   private boolean preLockless;    // 为true,当一个索引段文件在无锁提交之前被写入
  11.   private long delGen;     // 当前版本(generation)删除时的gen的值为delGen(即segments_delGen.del)
  12.    
  13.   private long[] normGen;    //   每个Field对应的norm文件的gen                                                
  14.          
  15.   private byte isCompoundFile;    // 如果为0,则表示不用检查;如果为1表示检查是否存在2.1版本之前的扩展名为.cfs和.nrm的文件                        
  16.   private boolean hasSingleNormFile;   //如果一个segments存在一个单独的norm文件。在当前版本中,false表示一个segments是由DocumentWriter生成的,true表示为最新创建的合并的索引段文件(包括复合segments文件和非复合segments文件)
  17.   
  18.   public SegmentInfo(String name, int docCount, Directory dir) {
  19.     this.name = name;
  20.     this.docCount = docCount;
  21.     this.dir = dir;
  22.     delGen = -1;// 初始化一个SegmentInfo的时候,指定存在标准化因子.nrm文件,和.del文件
  23.     isCompoundFile = 0;
  24.     preLockless = true;
  25.     hasSingleNormFile = false;
  26.   }
  27.   public SegmentInfo(String name, int docCount, Directory dir, boolean isCompoundFile, boolean hasSingleNormFile) { 
  28.     this(name, docCount, dir);
  29.     this.isCompoundFile = (byte) (isCompoundFile ? 1 : -1);
  30.     this.hasSingleNormFile = hasSingleNormFile;
  31.     preLockless = false// 为false,当一个索引段文件不是在无锁提交之前被写入
  32.   }
  33.   /**
  34.    *  根据指定的SegvmentInfo,拷贝它到我们当前的SegmentInfo实例中,
  35.    *  其实就是重新构造一个SegmentInfo,重新构造的该SegmentInfo与指
  36.    *  定的SegmentInfo src是相同的
  37.    */
  38.   void reset(SegmentInfo src) {
  39.     name = src.name;
  40.     docCount = src.docCount;
  41.     dir = src.dir;
  42.     preLockless = src.preLockless;
  43.     delGen = src.delGen;
  44.     if (src.normGen == null) {
  45.       normGen = null;
  46.     } else {
  47.       normGen = new long[src.normGen.length];
  48.       System.arraycopy(src.normGen, 0, normGen, 0, src.normGen.length);
  49.     }
  50.     isCompoundFile = src.isCompoundFile;
  51.     hasSingleNormFile = src.hasSingleNormFile;
  52.   }
  53.   /**
  54.     构造一个SegmentInfo对象,通过构造一个索引输入流对象IndexInput
  55.    */
  56.   public SegmentInfo(Directory dir, int format, IndexInput input) throws IOException {
  57.     this.dir = dir;
  58.     name = input.readString();
  59.     docCount = input.readInt();
  60.     if (format <= SegmentInfos.FORMAT_LOCKLESS) {
  61.       delGen = input.readLong();
  62.       if (format <= SegmentInfos.FORMAT_SINGLE_NORM_FILE) {
  63.         hasSingleNormFile = (1 == input.readByte());
  64.       } else {
  65.         hasSingleNormFile = false;
  66.       }
  67.       int numNormGen = input.readInt();
  68.       if (numNormGen == -1) {
  69.         normGen = null;
  70.       } else {
  71.         normGen = new long[numNormGen];
  72.         for(int j=0;j<numNormGen;j++) {
  73.           normGen[j] = input.readLong();
  74.         }
  75.       }
  76.       isCompoundFile = input.readByte();
  77.       preLockless = isCompoundFile == 0;
  78.     } else {
  79.       delGen = 0;
  80.       normGen = null;
  81.       isCompoundFile = 0;
  82.       preLockless = true;
  83.       hasSingleNormFile = false;
  84.     }
  85.   }
  86.   
  87.   /**
  88.    * 为每个Field的norm文件设置gen值(相当于初始化)
  89.    */
  90.   void setNumFields(int numFields) {
  91.     if (normGen == null) {
  92.       // normGen is null if we loaded a pre-2.1 segment
  93.       // file, or, if this segments file hasn't had any
  94.       // norms set against it yet:
  95.       normGen = new long[numFields];
  96.       if (!preLockless) {
  97.         // This is a FORMAT_LOCKLESS segment, which means
  98.         // there are no norms:
  99.         for(int i=0;i<numFields;i++) {
  100.           normGen[i] = -1;
  101.         }
  102.       }
  103.     }
  104.   }
  105.   boolean hasDeletions()
  106.     throws IOException {
  107.     // Cases:
  108.       
  109.     //   delGen == -1: this means this segment was written
  110.     //     by the LOCKLESS code and for certain does not have
  111.     //     deletions yet
  112.     //
  113.     //   delGen == 0: this means this segment was written by
  114.     //     pre-LOCKLESS code(无锁提交之前模式) which means we must check
  115.     //     directory to see if .del file exists
  116.     //
  117.     //   delGen > 0: this means this segment was written by
  118.     //     the LOCKLESS code(无锁提交模式) and for certain has
  119.     //     deletions
  120.     //
  121.     if (delGen == -1) {
  122.       return false;
  123.     } else if (delGen > 0) {
  124.       return true;
  125.     } else {
  126.       return dir.fileExists(getDelFileName());
  127.       // getDelFileName()在后面定义了该方法,获取删除的该索引目录下的文件名
  128.     }
  129.   }
  130.   void advanceDelGen() {
  131.     // delGen 0 is reserved for pre-LOCKLESS format
  132.     if (delGen == -1) {
  133.       delGen = 1;
  134.     } else {
  135.       delGen++;
  136.     }
  137.   }
  138.   void clearDelGen() {
  139.     delGen = -1;
  140.   }
  141.   public Object clone () {
  142.     SegmentInfo si = new SegmentInfo(name, docCount, dir);
  143.     si.isCompoundFile = isCompoundFile;
  144.     si.delGen = delGen;
  145.     si.preLockless = preLockless;
  146.     si.hasSingleNormFile = hasSingleNormFile;
  147.     if (normGen != null) {
  148.       si.normGen = (long[]) normGen.clone();
  149.     }
  150.     return si;
  151.   }
  152.   /**
  153.    * 获取删除的该索引目录下的文件名
  154.    */
  155.   String getDelFileName() {
  156.     if (delGen == -1) {
  157.       // In this case we know there is no deletion filename
  158.       // against this segment
  159.       return null;
  160.     } else {
  161.       // If delGen is 0, it's the pre-lockless-commit file format
  162.       return IndexFileNames.fileNameFromGeneration(name, ".del", delGen);
  163.       // 即返回文件名为name_delGen.del,例如segments_5.del
  164.     }
  165.   }
  166.   /**
  167.    * Returns true if this field for this segment has saved a separate norms file (_<segment>_N.sX).
  168.    * 如果该索引段的这个Field作为separate norms文件(_<segment>_N.sX)进行存储
  169.    *
  170.    * fieldNumber是一个需要检查的field的索引
  171.    */
  172.   boolean hasSeparateNorms(int fieldNumber)
  173.     throws IOException {
  174.       /**
  175.        * 不了解为什么使用normGen == null && preLockless作为判断条件
  176.        * 应该是preLockless为true表示有norm文件,why?
  177.        */
  178.     if ((normGen == null && preLockless) || (normGen != null && normGen[fieldNumber] == 0)) {
  179.       // Must fall back to directory file exists check:
  180.       String fileName = name + ".s" + fieldNumber;
  181.       return dir.fileExists(fileName);
  182.     } else if (normGen == null || normGen[fieldNumber] == -1) {
  183.       return false;
  184.     } else {
  185.       return true;
  186.     }
  187.   }
  188.   /**
  189.    * Returns true if any fields in this segment have separate norms.
  190.    */
  191.   boolean hasSeparateNorms()
  192.     throws IOException {
  193.     if (normGen == null) {
  194.       if (!preLockless) {
  195.         // This means we were created w/ LOCKLESS code and no
  196.         // norms are written yet:
  197.         return false;
  198.       } else {
  199.         // This means this segment was saved with pre-LOCKLESS
  200.         // code.  So we must fall back to the original
  201.         // directory list check:
  202.         // segment使用pre-LOCKLESS模式保存,需要回退到最初的目录下,进行核查
  203.         String[] result = dir.list();
  204.         String pattern;
  205.         pattern = name + ".s";
  206.         int patternLength = pattern.length();
  207.         for(int i = 0; i < result.length; i++){
  208.           if(result[i].startsWith(pattern) && Character.isDigit(result[i].charAt(patternLength)))
  209.             return true;
  210.         }
  211.         return false;
  212.       }
  213.     } else {
  214.       // NormGen!=NULL
  215.       // This means this segment was saved with LOCKLESS
  216.       // code so we first check whether any normGen's are >
  217.       // 0 (meaning they definitely have separate norms):
  218.       // 检查是否任何一个normGen都是 >= 1的
  219.       for(int i=0;i<normGen.length;i++) {
  220.         if (normGen[i] > 0) {
  221.           return true;
  222.         }
  223.       }
  224.       // Next we look for any == 0.  These cases were
  225.       // pre-LOCKLESS and must be checked in directory:
  226.       for(int i=0;i<normGen.length;i++) {
  227.         if (normGen[i] == 0) {
  228.           if (hasSeparateNorms(i)) {
  229.             return true;
  230.           }
  231.         }
  232.       }
  233.     }
  234.     return false;
  235.   }
  236.   /**
  237.    * Increment the generation count for the norms file for
  238.    * this field.
  239.    * 为每个Field的norm文件的gen,执行加1操作
  240.    * 
  241.    * @param fieldIndex:指定的Field的norm文件需要被重写,
  242.    * fieldIndex即对应的norm文件的gen值
  243.    */
  244.   void advanceNormGen(int fieldIndex) {
  245.     if (normGen[fieldIndex] == -1) {
  246.       normGen[fieldIndex] = 1;
  247.     } else {
  248.       normGen[fieldIndex]++;
  249.     }
  250.   }
  251.   /**
  252.    * Get the file name for the norms file for this field.
  253.    *
  254.    * @param number field index;number是一个Field的索引值
  255.    */
  256.   String getNormFileName(int number) throws IOException {
  257.     String prefix;
  258.     long gen;
  259.     if (normGen == null) {
  260.       gen = 0;
  261.     } else {
  262.       gen = normGen[number];//根据Field的索引值获取它对应的norm文件的gen值,然后使用该gen值取得索引段文件的文件名
  263.     }
  264.     
  265.     if (hasSeparateNorms(number)) {
  266.       // case 1: separate norm
  267.       prefix = ".s";
  268.       return IndexFileNames.fileNameFromGeneration(name, prefix + number, gen);
  269.       //使用gen值取得索引段文件的文件名,如果name=“segments”,number=7,gen=4,则返回的文件名为segments_4.s7
  270.     }
  271.     if (hasSingleNormFile) { //   如果存在一个单独的norm文件
  272.       // case 2: lockless (or nrm file exists) - single file for all norms 
  273.       prefix = "." + IndexFileNames.NORMS_EXTENSION;
  274.       // IndexFileNames.NORMS_EXTENSION=nrm
  275.       return IndexFileNames.fileNameFromGeneration(name, prefix, 0);
  276.       // 如果name=“segments”,则返回的文件名为segments.nrm 
  277.     }
  278.       
  279.       // case 3: norm file for each field
  280.     prefix = ".f";
  281.     return IndexFileNames.fileNameFromGeneration(name, prefix + number, 0);
  282.       //  如果name=“segments”,number=7,则返回的文件名为segments.f7
  283.   }
  284.   /**
  285.    * Mark whether this segment is stored as a compound file.
  286.    *
  287.    * @param isCompoundFile true if this is a compound file;
  288.    * else, false
  289.    */
  290.   void setUseCompoundFile(boolean isCompoundFile) {
  291.     if (isCompoundFile) {
  292.       this.isCompoundFile = 1;
  293.     } else {
  294.       this.isCompoundFile = -1;
  295.     }
  296.   }
  297.   /**
  298.    * Returns true if this segment is stored as a compound
  299.    * file; else, false.
  300.    */
  301.   boolean getUseCompoundFile() throws IOException {
  302.     if (isCompoundFile == -1) {
  303.       return false;
  304.     } else if (isCompoundFile == 1) {
  305.       return true;
  306.     } else {
  307.       return dir.fileExists(name + ".cfs");
  308.     }
  309.   }
  310.   
  311.   /**
  312.    * Save this segment's info.
  313.    * 
  314.    * 保存segment的信息,其实就是输出(写入)到磁盘中的索引目录中
  315.    */
  316.   void write(IndexOutput output)
  317.     throws IOException {
  318.     output.writeString(name);
  319.     output.writeInt(docCount);
  320.     output.writeLong(delGen);
  321.     output.writeByte((byte) (hasSingleNormFile ? 1:0));
  322.     if (normGen == null) {
  323.       output.writeInt(-1);
  324.     } else {
  325.       output.writeInt(normGen.length);
  326.       for(int j = 0; j < normGen.length; j++) {
  327.         output.writeLong(normGen[j]);
  328.       }
  329.     }
  330.     output.writeByte(isCompoundFile);
  331.   }
  332. }

我现在看得是lucene2.1.0版本,恰好是segmentinfo类更新的分界点。原来的版本里面的SegmentInfo类的实现非常简单。

  1. package org.apache.lucene.index;
  2. import org.apache.lucene.store.Directory;
  3. final class SegmentInfo {
  4. public String name;      // unique name in dir
  5. public int docCount;      // number of docs in seg
  6. public Directory dir;      // where segment resides
  7. public SegmentInfo(String name, int docCount, Directory dir) {
  8.     this.name = name;
  9.     this.docCount = docCount;
  10.     this.dir = dir;
  11. }
  12. }

Lucene2.1及其以后的版本,SegmentInfo类的实现就变得比较复杂了。尤其在写入Norm文件这方面。下面就罗列版本前后的一些差别:

【在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和<Norms> NumFieldsWithNorms 这两部分。

其中:

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

public static final int FORMAT = -1;

<Norms>对应这一个<Byte>,而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.1版本以前的,norm文件的形式有:segments.fN、segments_N.sX两种。其中N是gen值,X是一个Field在Document中的索引值。

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值