Lucene-2.3.1 源代码阅读学习(23)

通过对DocumentWriter类的writePostings()方法进行学习。同时,研究并解决几个我一直感到困惑的几个类的用途,以及到底怎样阐述能使自己有一种感性的认识。

 

 

 

writePostings()方法的实现

writePostings()方法是对已经经过倒排的文档,将词条的一些有用信息写入到索引段文件中。该方法的实现如下所示:

private final void writePostings(Posting[] postings, String segment)
          throws CorruptIndexException, IOException {
    IndexOutput freq = null, prox = null;
    TermInfosWriter tis = null;    // TermInfosWriter类是与词条的写操作有关的
    TermVectorsWriter termVectorWriter = null;
    try {
     // 打开文件流,为倒排的索引进行存储
      freq = directory.createOutput(segment + ".frq");    //   打开segments.frq文件
      prox = directory.createOutput(segment + ".prx");    // 打开segments.prx文件
      tis = new TermInfosWriter(directory, segment, fieldInfos,termIndexInterval);    // 创建一个TermInfosWriter对象
      TermInfo ti = new TermInfo();    // 创建一个TermInfo对象,该对象用于在内存中管理词条的
      String currentField = null;
      boolean currentFieldHasPayloads = false;
     
      for (int i = 0; i < postings.length; i++) {    // 遍历Posting数组中的每以个对象
        Posting posting = postings[i];   

        // 检查:是否需要转换成一个新的Field
        String termField = posting.term.field();
        if (currentField != termField) {    // 从Posting数组中获取Field的名称(Strnig类型)如果不为null
          // 改变currentField。看是否有需要存储的信息
          currentField = termField;
          FieldInfo fi = fieldInfos.fieldInfo(currentField);    // 根据currentField名称从FieldInfos中找到这个FieldInfo对象
          currentFieldHasPayloads = fi.storePayloads;
          if (fi.storeTermVector) {
            if (termVectorWriter == null) {
              termVectorWriter =
                new TermVectorsWriter(directory, segment, fieldInfos);    // 构造一个TermVectorsWriter对象,该对象与词条向量的写操作相关
              termVectorWriter.openDocument();
            }
            termVectorWriter.openField(currentField);    // 根据指定的Field的名称currentField打开一个文件输出流

          } else if (termVectorWriter != null) {
            termVectorWriter.closeField();
          }
        }
       
        // 为带有指针的sengments.frq文件和segments.prx文件设置一个入口
        ti.set(1, freq.getFilePointer(), prox.getFilePointer(), -1); // ti是一个TernInfo类实例,用来管理词条的
        tis.add(posting.term, ti);    // tis是一个TermInfosWriter类实例,它的方法为add(Term term, TermInfo ti),将一个<Term, TermInfo>对加入到其中

        // 为segments.frq文件添加一个入口
        int postingFreq = posting.freq;
        if (postingFreq == 1)      // optimize freq=1
          freq.writeVInt(1);     // set low bit of doc num.
        else {
          freq.writeVInt(0);     // the document number
          freq.writeVInt(postingFreq);     // frequency in doc
        }

        int lastPosition = 0;     // write positions
        int[] positions = posting.positions;
        Payload[] payloads = posting.payloads;
        int lastPayloadLength = -1;
       
        // 下面是对词条Term的Payload和positions信息进行优化处理,写入输出到索引目录中
        // The following encoding is being used for positions and payloads:
        // Case 1: current field does not store payloads
        //           Positions     -> <PositionDelta>^freq
        //           PositionDelta -> VInt
        //         The PositionDelta is the difference between the current
        //         and the previous position
        // Case 2: current field stores payloads
        //           Positions     -> <PositionDelta, Payload>^freq
        //           Payload       -> <PayloadLength?, PayloadData>
        //           PositionDelta -> VInt
        //           PayloadLength -> VInt
        //           PayloadData   -> byte^PayloadLength
        //         In this case PositionDelta/2 is the difference between
        //         the current and the previous position. If PositionDelta
        //         is odd, then a PayloadLength encoded as VInt follows,
        //         if PositionDelta is even, then it is assumed that the
        //         length of the current Payload equals the length of the
        //         previous Payload.       
        for (int j = 0; j < postingFreq; j++) {    // 用希腊字母编码
          int position = positions[j];
          int delta = position - lastPosition;
          if (currentFieldHasPayloads) {
            int payloadLength = 0;
            Payload payload = null;
            if (payloads != null) {
              payload = payloads[j];
              if (payload != null) {
                payloadLength = payload.length;
              }
            }
            if (payloadLength == lastPayloadLength) {
            // the length of the current payload equals the length
            // of the previous one. So we do not have to store the length
            // again and we only shift the position delta by one bit
              prox.writeVInt(delta * 2);
            } else {
            // the length of the current payload is different from the
            // previous one. We shift the position delta, set the lowest
            // bit and store the current payload length as VInt.
              prox.writeVInt(delta * 2 + 1);
              prox.writeVInt(payloadLength);
              lastPayloadLength = payloadLength;
            }
            if (payloadLength > 0) {
            // write current payload
              prox.writeBytes(payload.data, payload.offset, payload.length);
            }
          } else {
          // field does not store payloads, just write position delta as VInt
            prox.writeVInt(delta);
          }
          lastPosition = position;
        }
        if (termVectorWriter != null && termVectorWriter.isFieldOpen()) {
            termVectorWriter.addTerm(posting.term.text(), postingFreq, posting.positions, posting.offsets);
        }
      }
      if (termVectorWriter != null)
        termVectorWriter.closeDocument();
    } finally {
      // 关闭所有的流对象
      IOException keep = null;
      if (freq != null) try { freq.close(); } catch (IOException e) { if (keep == null) keep = e; }
      if (prox != null) try { prox.close(); } catch (IOException e) { if (keep == null) keep = e; }
      if (tis != null) try { tis.close(); } catch (IOException e) { if (keep == null) keep = e; }
      if (termVectorWriter != null) try { termVectorWriter.close(); } catch (IOException e) { if (keep == null) keep = e; }
      if (keep != null) throw (IOException) keep.fillInStackTrace();
    }
}

上面的writePostings()方法,将与词条相关的一些信息写入到索引目录中,指定的两个文件中,它们是.frq文件(与此条频率有关的)和.prx文件(该文件存储了与词条的位置有关的信息)。

[.fnm文件存储了一个Document中的所有Field的名称,即扩展名由来:Field's name]

关于Posting类

Posting类定义在DocumentWriter类的内部。

在DocumentWriter类的addPosition方法中的最后两行可以看到:

      Term term = new Term(field, text, false);
      postingTable.put(term, new Posting(term, position, payload, offset));

一个postingTable是一个HashMap,存储的是一个个的<键,值>对。它的键是Term对象,值是Posting对象。现在看看Term和Posting到底拥有哪些信息。

1、关于Term类,它的源代码如下所示:

package org.apache.lucene.index;

public final class Term implements Comparable, java.io.Serializable {
String field;    // 一个Field的名称
String text;    // 一个词条的文本内容

//   通过Field的名称和词条的文本内容构造一个词条
public Term(String fld, String txt) {
    this(fld, txt, true);
}

/* 当打开一个DocumentWriter的时候,会存在一个字符串池(pool),该池中具有Term的field字段,即Field的名称。如果对另一个Field进行分词后,如果产生的词条Term的field(是一个String字符串)在当前的字符串池中已经存在,则返回该字符串field的引用;如果新产生的词条Term的field在当前的字符串池中不存在,说明是一个新的词条(即当前处理的这些词条Term中没有名称为field的词条,也就是,添加到Document中的Field是一个新的Field)则将该Term的field加入到字符串池中。

*/
Term(String fld, String txt, boolean intern) {
    field = intern ? fld.intern() : fld;   // field names are interned
    text = txt;       // unless already known to be
}

public final String field() { return field; }

    public final String text() { return text; }

/**
   * 优化已经构造出来的词条Term。返回一个新的词条(与被优化的那个词条具有相同的名称)。
   */

public Term createTerm(String text)
{
      return new Term(field,text,false);
}

/    如果两个词条Term具有相同的field和text,则返回true
public final boolean equals(Object o) {
    if (o == this)
      return true;
    if (o == null)
      return false;
    if (!(o instanceof Term))
      return false;
    Term other = (Term)o;
    return field == other.field && text.equals(other.text);
}

// 返回一个词条的field和the text的哈希码之和
public final int hashCode() {
    return field.hashCode() + text.hashCode();
}

public int compareTo(Object other) {
    return compareTo((Term)other);
}

// 定制客户化排序方式
public final int compareTo(Term other) {
    if (field == other.field)     // fields are interned
      return text.compareTo(other.text);
    else
      return field.compareTo(other.field);
}

// 重新设置一个词条Term的名称field和文本信息text
final void set(String fld, String txt) {
    field = fld;
    text = txt;
}

public final String toString() { return field + ":" + text; }

// 根据一个打开的对象输入流,从字符串池中读取一个Term的名称field

private void readObject(java.io.ObjectInputStream in)
    throws java.io.IOException, ClassNotFoundException
{
      in.defaultReadObject();
      field = field.intern();
}
}

Term是文本中的一个词(即word),它是检索的基本单位。

2、关于Posting类,它的源代码如下所示:

final class Posting {      // 关于在Document中的一个词条Term的信息
Term term;       // 一个Term对象
int freq;       // 该指定词条Term在Document中的频率
int[] positions;      // 该词条的位置数组,因为在一个Document中可能存在多个相同的该Term,所有它的位置不是唯一的
Payload[] payloads;   // 该词条Term的payload信息
TermVectorOffsetInfo [] offsets;    // 该词条向量的偏移量数组

Posting(Term t, int position, Payload payload, TermVectorOffsetInfo offset) {
    term = t;
    freq = 1;
    positions = new int[1];
    positions[0] = position;
   
    if (payload != null) {
      payloads = new Payload[1];
      payloads[0] = payload;
    } else
      payloads = null;

    if(offset != null){
      offsets = new TermVectorOffsetInfo[1];
      offsets[0] = offset;
    } else
      offsets = null;
}
}

3、关于Payload类

上面,Posting类中讲一个Payload[]数组作为它的成员,到底Payload如何定义?从它的源代码解读:

package org.apache.lucene.index;

import java.io.Serializable;

import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.TokenStream;

/**
一个Payload是一个元数据(就是数据的数据) ,在Posting类中用到它,说明它是一个用于描述Posting对象的(构成和特征)。进行分词处理的时候,没切出一个词条Term,即这个词条存在了,则同时存储一个Payload信息。当把一个<Term,Posting>对放到postTable中的时候,就为该Posting增加了一个Payload元数据。

这个元数据最早被添加到哪个类的对象里了呢?从DocumentWriter类的invertDocument()方法中,可以看到对该类的addPosition()方法的调用,这调用addPosition()方法的时候,传了一个Payload,继续看这个Playload的来源,可以看到,是在一个TokenStream打开,进行分词的过程中,从一个Token中获取到的Payload,即 Payload payload = t.getPayload();,这里t是一个Token对象。

继续看addPostition()方法,这行代码ti.payloads[freq] = payload;,其中ti是Posting的实例,每个ti(即Posting)对应着一个Pauload[]数组:ti.payloads = new Payload[ti.positions.length];,这个数组的大小为这个Posting的位置数组positions[]的长度。

上面就是Payload的来源,及其一些去向和处理。

之后,根据Payload构造了一个Posting对象,并且在invertDocument()方法中进行分词,根据切出来的词构造了一个词条Term,将Posting实例和Term实例作为一个<Term,Posting>对放到postTable中,如下所示:

      Term term = new Term(field, text, false);
       postingTable.put(term, new Posting(term, position, payload, offset));

*/    

public class Payload implements Serializable {
    // payload数据的byte数组
    protected byte[] data;
   
    // 在byte数组中的偏移量
    protected int offset;
   
    // payload的长度
    protected int length;
   
    // 创建一个空的payload
    protected Payload() {
    }
   
    // 根据一个字节数组,即Payload的内容,创建一个Payload
    public Payload(byte[] data) {
      this(data, 0, data.length);
    }

    // 创建一个Payload,指定了它的内容data(一个字节数组) 、字节数组的偏移量、data的长度
    public Payload(byte[] data, int offset, int length) {
      if (offset < 0 || offset + length > data.length) {
        throw new IllegalArgumentException();
      }
      this.data = data;
      this.offset = offset;
      this.length = length;
    }
   
    // 返回Payload内容的长度
    public int length() {
      return this.length;
    }
   
    // 返回指定索引位置的一个字节byte
    public byte byteAt(int index) {
      if (0 <= index && index < this.length) {
        return this.data[this.offset + index];   
      }
      throw new ArrayIndexOutOfBoundsException(index);
    }
   
   // 创建一个字节数组(分配了空间),并将Payload的信息拷贝到该字节数组中
    public byte[] toByteArray() {
      byte[] retArray = new byte[this.length];
      System.arraycopy(this.data, this.offset, retArray, 0, this.length);
      return retArray;
    }
   
    // 将Payload的数据拷贝到指定的一个字节数组中
    public void copyTo(byte[] target, int targetOffset) {
      if (this.length > target.length + targetOffset) {
        throw new ArrayIndexOutOfBoundsException();
      }
      System.arraycopy(this.data, this.offset, target, targetOffset, this.length);
    }
}

现在,用该对Posting、Payload有了进一步的了解了,一个TokenStream打开以后,进行分词处理,返回的是Token,然后从Token中提取有用的信息,来构造我们需要的词条Term,这时一个词条Term就诞生了。因为一个词条是静态的,并不能反映在实际应用中的动态变化轨迹,所以又使用Posting类对一个Term进行封装,为一个词条赋予了更加丰富的内容。

你可能感兴趣的:(apache,Lucene,J#)