Lucene源代码之FieldInfos容器

      FieldInfos是一个容器类,含有如下属性:

static final byte IS_INDEXED = 0x1;
static final byte STORE_TERMVECTOR = 0x2;
static final byte STORE_POSITIONS_WITH_TERMVECTOR = 0x4;
static final byte STORE_OFFSET_WITH_TERMVECTOR = 0x8;
static final byte OMIT_NORMS = 0x10;

      通过Field的编号和名字来保存field的信息

private ArrayList byNumber = new ArrayList();
private HashMap byName = new HashMap();

      下面将逐个介绍它的方法:

      add()方法;

public void add(String name, boolean isIndexed, boolean storeTermVector,
                  boolean storePositionWithTermVector, boolean storeOffsetWithTermVector, boolean omitNorms) {
    FieldInfo fi = fieldInfo(name);
    if (fi == null) {
      addInternal(name, isIndexed, storeTermVector, storePositionWithTermVector, storeOffsetWithTermVector, omitNorms);
    } else {
      if (fi.isIndexed != isIndexed) {
        fi.isIndexed = true;                      // once indexed, always index
      }
      if (fi.storeTermVector != storeTermVector) {
        fi.storeTermVector = true;                // once vector, always vector
      }
      if (fi.storePositionWithTermVector != storePositionWithTermVector) {
        fi.storePositionWithTermVector = true;                // once vector, always vector
      }
      if (fi.storeOffsetWithTermVector != storeOffsetWithTermVector) {
        fi.storeOffsetWithTermVector = true;                // once vector, always vector
      }
      if (fi.omitNorms != omitNorms) {
        fi.omitNorms = false;                // once norms are stored, always store
      }

    }
}

      从中又可以看到:FieldInfos容器类是通过add方法调用addInterval方法将field信息加入的;代码如下:

private void addInternal(String name, boolean isIndexed,boolean storeTermVector, 
                         boolean storePositionWithTermVector, boolean storeOffsetWithTermVector,
                         boolean omitNorms) {
    FieldInfo fi =
      new FieldInfo(name, isIndexed, byNumber.size(), storeTermVector, storePositionWithTermVector,
              storeOffsetWithTermVector, omitNorms);
    byNumber.add(fi);
    byName.put(name, fi);
}

      那么FieldInfo是如何构建的呢?

FieldInfo(String na, boolean tk, int nu, boolean storeTermVector, 
            boolean storePositionWithTermVector,  boolean storeOffsetWithTermVector, boolean omitNorms) {
    name = na;
    isIndexed = tk;
    number = nu;
    this.storeTermVector = storeTermVector;
    this.storeOffsetWithTermVector = storeOffsetWithTermVector;
    this.storePositionWithTermVector = storePositionWithTermVector;
    this.omitNorms = omitNorms;
}

       在这里顺便理一下过程:

       首先,在DocumentWriter中的addDocument()方法中构造了一个FieldInfos对象,将传递进来的doc加入到其中。从doc中提取关于Field的信息,将操作频繁的信息提取出来,构造一个个的FieldInfo对象。然后FieldInfos在对FieldInfo进行管理。

       其次,在addDocument()方法中利用FieldInfos管理FieldInfo的便利性,再次提取Field的信息,构造Posting类的实例 。在一个FieldInfos写入到索引文件之前,要对doc进行倒排(因为doc已经加入到FieldInfos中了),倒排的过程中对Field进行了分词处理。

       再次,对doc倒排之后,形成一个Posting[]数组,接着对它进行排序(使用快速排序),之后FieldInfos才能将各个Field的名称写入索引目录中fieldInfos.write(directory, segment + ".fnm");。如果传递进来的segment值为_ram_1它写到了文件_ram_1.fnm文件中。

       也就是说,当postingTable排序完毕,term位置信息生成结束,Lucene会首先调用fieldInfos中的write方法,生成.fnm文件,写入索引目录

       FieldInfos中的两个核心write方法如下:

public void write(Directory d, String name) throws IOException {
    IndexOutput output = d.createOutput(name);                                                 
    try {
      write(output);                                                                                                  
    } finally {
      output.close();
    }
}

       @1:在索引目录下中生成.fnm文件;返回了一个有name构造的RAMOutputStream输内存出流对象output。

       注:这里面传递进来的 d 是实现类RAMDirectory的实例。

见附录1

       @2:向文件中写入数据。调用核心代码write方法;

 public void write(IndexOutput output) throws IOException {
    output.writeVInt(size());                                                                                                      #1
    for (int i = 0; i < size(); i++) {a
      FieldInfo fi = fieldInfo(i);
      byte bits = 0x0;
      if (fi.isIndexed) bits |= IS_INDEXED;
      if (fi.storeTermVector) bits |= STORE_TERMVECTOR;
      if (fi.storePositionWithTermVector) bits |= STORE_POSITIONS_WITH_TERMVECTOR;
      if (fi.storeOffsetWithTermVector) bits |= STORE_OFFSET_WITH_TERMVECTOR;
      if (fi.omitNorms) bits |= OMIT_NORMS;
      output.writeString(fi.name);                                                                                                #2
      output.writeByte(bits);                                                                                                       #3
    }b
}

       #1: output.writeVInt(size());将FieldInfos中FieldInfo的个数的数值写入到当前的缓冲区中了,即写入Field的个数。

public int size() {
   return byNumber.size();
}

       #2:将FieldInfo的name写入输出流output中,先写入name的长度,再写字符;

public void writeString(String s)throws IOException
{
   int length=s.length();
   writeVInt(length);
   writeChars(s,0,length);
}

       #3:将处理过的属性写入输出流output;

       a~b:通过一个for循环,根据指定的位置索引(FieldInfo的编号),遍历每个FieldInfo,对其一些属性进行处理后写入,其实就是通过一个字节来识别各个属性(比如是否索引、是否存储词条向量、是否忽略norms、是否存储Payload信息),最后将FieldInfo的name和这些经过处理的属性写入到输出流中。

       对FieldInfo类和FieldInfos类进行总结:

       1、FieldInfo作为一个实体,保存了Field的一些主要的信息;

       2、因为对Field的操作比较频繁,而每次管理都在内存中加载FieldInfo这个轻量级但信息很重要的对象,能够大大提高建立索引的速度;

       3、FieldInfos包含的信息比较丰富,通过一个FieldInfo对象,调出Document中的Field到内存中,对每个Field进行详细的管理。

       4、FieldInfos支持独立从索引目录中读取Document中的信息(主要根据Document参数,管理其中的Field),然后再写回到索引目录。

综合总结:

       FieldInfos是对Document中的Field进行管理的,它主要是在内存中进行管理,然后写入到索引目录中。具体地,它所拥有的信息都被写入了一个与索引段文件相关的segments.fnm文件中 ,在DocumentWriter类的addDocument()方法中可以看到,DocumnetWriter类的实现,是在FieldInfos类的基础上。FieldInfos类对Document的所有的Field的静态信息进行管理,而DocumentWriter类表现出了更强大的管理Document的功能,主要是对Field进行了一些高级的操作,比如使用Analyzer分析器进行分词、对切分出来的词条进行排序(文档倒排)等等。

附录1

      根据传进来的name,使用RAMDirectory的createOutput()方法创建一个输出流,即:IndexOutput output = d.createOutput(name);这时,可以看到,接下来的一些操作都转入到RAMDirectory中去了。也就是根据一个name参数,我们看一下name在进入到RAMDirectory中的过程中发生了怎样的变化。

       RAMDirectory的createOutput()方法定义如下所示:

public IndexOutput createOutput(String name) {
    ensureOpen();
    RAMFile file = new RAMFile(this);
    synchronized (this) {
      RAMFile existing = (RAMFile)fileMap.get(name);
      if (existing!=null) {
        sizeInBytes -= existing.sizeInBytes;
        existing.directory = null;
      }
      fileMap.put(name, file);
    }
    return new RAMOutputStream(file);
}

 

       RAMDirectory拥有一个成员变量fileMap(它是一个HashMap),在调用ensureOpen()方法时,通过fileMap来判断,如果fileMap=null,则抛出异常,终止程序流程。所以当fileMap!=null的时候说明RAMDirectory是处于打开状态的。

       根据已经处于打开状态的this(即RAMDirectory),以其作为参数构造一个 RAMFile file。接着,以当前打开的RAMDirectory作为同步信号量,从fileMap中获取名称为name的一个RAMFile的实例existing,如果内存中不存在名称为name的RAMFile,则要把该RAMFile添加到fileMap中,即fileMap.put(name, file);,同时直接返回以当前RAMDirectory作为参数构造的RAMFile file;如果RAMFile existing存在,则以该name构造的RAMField作为参数,添加到RAMOutputStream内存输出流中。

      这里RAMDirectory的sizeInBytes成员指定了当前RAMDirectory中操作的字节数,而对于每个RAMFile都与一个RAMDirectory相关,所以,当从内存中把一个RAMFile放到内存输出流RAMOutputStream中时,当前内存中的字节数便减少了该RAMFile所具有的字节数,即 sizeInBytes -= existing.sizeInBytes;。同时,这个RAMFile进入到了RAMOutputStream中,即表示该文件已经不与RAMDirectory相关了,所以上面有existing.directory = null;。

附录2

关于IndexOutput的writeVInt()方法

       方法定义如下所示:

public void writeVInt(int i) throws IOException {
    while ((i & ~0x7F) != 0) {
      writeByte((byte)((i & 0x7f) | 0x80));
      i >>>= 7;
    }
    writeByte((byte)i);
}

       该方法中,0x7F的值为127,按位取反后~0x7F的值为-128。

当0<=i<=127时,(i & ~0x7F) =0;当128<=i<=255时,(i & ~0x7F) =128;当256<=i<=511时,(i & ~0x7F) =256;以此类推。

       也就是说,判断的条件不等于0成立 ,i的取值范围是i>=128。

      调用了RAMOutputStream类的writeBytes()方法,写入的字节值为(i & 0x7f) | 0x80,因为,(i & ~0x7F)的值为0,128,256,384,512……,再与0x80(即128)做按位或运算,也就是当(i & 0x7f) 的值为0时,写入的字节是128,从而(i & 0x7f) | 0x80为写入的值:128,256,384,512……,没有0了。

关于RAMOutputStream类的writeByte()方法

public void writeByte(byte b) throws IOException {
    if (bufferPosition >= BUFFER_SIZE)
      flush();
    buffer[bufferPosition++] = b;
}

 

关于RAMOutputStream类的writeBytes()方法

       output是RAMOutputStream类的一个实例,所以在操作字节时使用了RAMOutputStream类的writeBytes()方法,定义如下:

public void writeByte(byte b) throws IOException {
    if (bufferPosition == bufferLength) {
      currentBufferIndex++;
      switchCurrentBuffer();
    }
    currentBuffer[bufferPosition++] = b;
}

 

       如果bufferPosition == bufferLength,即缓冲区满,需要动态增加当前buffer的容量,索引位置增加1,当前索引位置在buffer的最后位置,然后调用switchCurrentBuffer()方法,实现字节缓冲区的动态分配:

private final void switchCurrentBuffer() throws IOException {
    if (currentBufferIndex == file.buffers.size()) {
      currentBuffer = file.addBuffer(BUFFER_SIZE);
    } else {
      currentBuffer = (byte[]) file.buffers.get(currentBufferIndex);
    }
    bufferPosition = 0;
    bufferStart = BUFFER_SIZE * currentBufferIndex;
    bufferLength = currentBuffer.length;
}

 

       否则。缓冲区未满,可以写入。

       把将待写入的经过处理的FieldInfo的个数数值,以128个字节为最小单位,写入到当前的缓冲区中,不够128个字节,填充到128个字节,不能截断数据。

转载于:https://my.oschina.net/u/3209682/blog/824179

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值