首先还是来看看,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源码:
- package org.apache.lucene.index;
- import org.apache.lucene.store.Directory;
- import org.apache.lucene.store.IndexOutput;
- import org.apache.lucene.store.IndexInput;
- import java.io.IOException;
- final class SegmentInfo {
- public String name; // unique name in dir
- public int docCount; // number of docs in seg
- public Directory dir; // where segment resides
- private boolean preLockless; // 为true,当一个索引段文件在无锁提交之前被写入
- private long delGen; // 当前版本(generation)删除时的gen的值为delGen(即segments_delGen.del)
- private long[] normGen; // 每个Field对应的norm文件的gen
- private byte isCompoundFile; // 如果为0,则表示不用检查;如果为1表示检查是否存在2.1版本之前的扩展名为.cfs和.nrm的文件
- private boolean hasSingleNormFile; //如果一个segments存在一个单独的norm文件。在当前版本中,false表示一个segments是由DocumentWriter生成的,true表示为最新创建的合并的索引段文件(包括复合segments文件和非复合segments文件)
- public SegmentInfo(String name, int docCount, Directory dir) {
- this.name = name;
- this.docCount = docCount;
- this.dir = dir;
- delGen = -1;// 初始化一个SegmentInfo的时候,指定存在标准化因子.nrm文件,和.del文件
- isCompoundFile = 0;
- preLockless = true;
- hasSingleNormFile = false;
- }
- public SegmentInfo(String name, int docCount, Directory dir, boolean isCompoundFile, boolean hasSingleNormFile) {
- this(name, docCount, dir);
- this.isCompoundFile = (byte) (isCompoundFile ? 1 : -1);
- this.hasSingleNormFile = hasSingleNormFile;
- preLockless = false; // 为false,当一个索引段文件不是在无锁提交之前被写入
- }
- /**
- * 根据指定的SegvmentInfo,拷贝它到我们当前的SegmentInfo实例中,
- * 其实就是重新构造一个SegmentInfo,重新构造的该SegmentInfo与指
- * 定的SegmentInfo src是相同的
- */
- void reset(SegmentInfo src) {
- name = src.name;
- docCount = src.docCount;
- dir = src.dir;
- preLockless = src.preLockless;
- delGen = src.delGen;
- if (src.normGen == null) {
- normGen = null;
- } else {
- normGen = new long[src.normGen.length];
- System.arraycopy(src.normGen, 0, normGen, 0, src.normGen.length);
- }
- isCompoundFile = src.isCompoundFile;
- hasSingleNormFile = src.hasSingleNormFile;
- }
- /**
- 构造一个SegmentInfo对象,通过构造一个索引输入流对象IndexInput
- */
- public 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 == -1) {
- normGen = null;
- } else {
- normGen = new long[numNormGen];
- for(int j=0;j<numNormGen;j++) {
- normGen[j] = input.readLong();
- }
- }
- isCompoundFile = input.readByte();
- preLockless = isCompoundFile == 0;
- } else {
- delGen = 0;
- normGen = null;
- isCompoundFile = 0;
- preLockless = true;
- hasSingleNormFile = false;
- }
- }
- /**
- * 为每个Field的norm文件设置gen值(相当于初始化)
- */
- void setNumFields(int numFields) {
- if (normGen == null) {
- // normGen is null if we loaded a pre-2.1 segment
- // file, or, if this segments file hasn't had any
- // norms set against it yet:
- normGen = new long[numFields];
- if (!preLockless) {
- // This is a FORMAT_LOCKLESS segment, which means
- // there are no norms:
- for(int i=0;i<numFields;i++) {
- normGen[i] = -1;
- }
- }
- }
- }
- boolean hasDeletions()
- throws IOException {
- // Cases:
- // delGen == -1: this means this segment was written
- // by the LOCKLESS code and for certain does not have
- // deletions yet
- //
- // delGen == 0: this means this segment was written by
- // pre-LOCKLESS code(无锁提交之前模式) which means we must check
- // directory to see if .del file exists
- //
- // delGen > 0: this means this segment was written by
- // the LOCKLESS code(无锁提交模式) and for certain has
- // deletions
- //
- if (delGen == -1) {
- return false;
- } else if (delGen > 0) {
- return true;
- } else {
- return dir.fileExists(getDelFileName());
- // getDelFileName()在后面定义了该方法,获取删除的该索引目录下的文件名
- }
- }
- void advanceDelGen() {
- // delGen 0 is reserved for pre-LOCKLESS format
- if (delGen == -1) {
- delGen = 1;
- } else {
- delGen++;
- }
- }
- void clearDelGen() {
- delGen = -1;
- }
- public Object clone () {
- 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 == -1) {
- // In this case we know there is no deletion filename
- // against this segment
- return null;
- } else {
- // If delGen is 0, it's the pre-lockless-commit file format
- return IndexFileNames.fileNameFromGeneration(name, ".del", delGen);
- // 即返回文件名为name_delGen.del,例如segments_5.del
- }
- }
- /**
- * Returns true if this field for this segment has saved a separate norms file (_<segment>_N.sX).
- * 如果该索引段的这个Field作为separate norms文件(_<segment>_N.sX)进行存储
- *
- * fieldNumber是一个需要检查的field的索引
- */
- boolean hasSeparateNorms(int fieldNumber)
- throws IOException {
- /**
- * 不了解为什么使用normGen == null && preLockless作为判断条件
- * 应该是preLockless为true表示有norm文件,why?
- */
- if ((normGen == null && preLockless) || (normGen != null && normGen[fieldNumber] == 0)) {
- // Must fall back to directory file exists check:
- String fileName = name + ".s" + fieldNumber;
- return dir.fileExists(fileName);
- } else if (normGen == null || normGen[fieldNumber] == -1) {
- return false;
- } else {
- return true;
- }
- }
- /**
- * Returns true if any fields in this segment have separate norms.
- */
- boolean hasSeparateNorms()
- throws IOException {
- if (normGen == null) {
- if (!preLockless) {
- // This means we were created w/ LOCKLESS code and no
- // norms are written yet:
- return false;
- } else {
- // This means this segment was saved with pre-LOCKLESS
- // code. So we must fall back to the original
- // directory list check:
- // segment使用pre-LOCKLESS模式保存,需要回退到最初的目录下,进行核查
- String[] result = dir.list();
- String pattern;
- pattern = 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
- // This means this segment was saved with LOCKLESS
- // code so we first check whether any normGen's are >
- // 0 (meaning they definitely have separate norms):
- // 检查是否任何一个normGen都是 >= 1的
- for(int i=0;i<normGen.length;i++) {
- if (normGen[i] > 0) {
- return true;
- }
- }
- // Next we look for any == 0. These cases were
- // pre-LOCKLESS and must be checked in directory:
- for(int i=0;i<normGen.length;i++) {
- if (normGen[i] == 0) {
- if (hasSeparateNorms(i)) {
- return true;
- }
- }
- }
- }
- return false;
- }
- /**
- * Increment the generation count for the norms file for
- * this field.
- * 为每个Field的norm文件的gen,执行加1操作
- *
- * @param fieldIndex:指定的Field的norm文件需要被重写,
- * fieldIndex即对应的norm文件的gen值
- */
- void advanceNormGen(int fieldIndex) {
- if (normGen[fieldIndex] == -1) {
- normGen[fieldIndex] = 1;
- } else {
- normGen[fieldIndex]++;
- }
- }
- /**
- * Get the file name for the norms file for this field.
- *
- * @param number field index;number是一个Field的索引值
- */
- String getNormFileName(int number) throws IOException {
- String prefix;
- long gen;
- if (normGen == null) {
- gen = 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, 0);
- // 如果name=“segments”,则返回的文件名为segments.nrm
- }
- // case 3: norm file for each field
- prefix = ".f";
- return IndexFileNames.fileNameFromGeneration(name, prefix + number, 0);
- // 如果name=“segments”,number=7,则返回的文件名为segments.f7
- }
- /**
- * Mark whether this segment is stored as a compound file.
- *
- * @param isCompoundFile true if this is a compound file;
- * else, false
- */
- void setUseCompoundFile(boolean isCompoundFile) {
- if (isCompoundFile) {
- this.isCompoundFile = 1;
- } else {
- this.isCompoundFile = -1;
- }
- }
- /**
- * Returns true if this segment is stored as a compound
- * file; else, false.
- */
- boolean getUseCompoundFile() throws IOException {
- if (isCompoundFile == -1) {
- return false;
- } else if (isCompoundFile == 1) {
- return true;
- } else {
- return dir.fileExists(name + ".cfs");
- }
- }
- /**
- * Save this segment's info.
- *
- * 保存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(-1);
- } else {
- output.writeInt(normGen.length);
- for(int j = 0; j < normGen.length; j++) {
- output.writeLong(normGen[j]);
- }
- }
- output.writeByte(isCompoundFile);
- }
- }
我现在看得是lucene2.1.0版本,恰好是segmentinfo类更新的分界点。原来的版本里面的SegmentInfo类的实现非常简单。
- 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;
- }
- }
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版本之前的一些版本中,对标准化因子设置文件进行处理。