Lucene源码分析 - BlockTreeTermsWriter 存储词典(Term Dictionary)与索引(Term Index)

本文详细分析了Lucene 6.3.0中的BlockTreeTermsWriter类,讲解了如何存储term词典和建立索引。BlockTreeTermsWriter使用FST构建term索引,包括Entry的存储结构、公共前缀判断和block的写入逻辑。文章还探讨了FST的构建和Entry集合的写入策略。
摘要由CSDN通过智能技术生成

本文的代码以lucene-core 6.3.0为准,包含BlockTreeTermsWriter的pushTerm函数,writeBlocks函数等整个类所有代码的解析。转载请注明出处。

0 基本信息

  • BlockTreeTermsWriter类主要逻辑是存储term词典,对term建立索引。
  • 假设输入字符串为["regular", "request", "rest", "teacher", "team", "teenage", "tend"],假设minItemsInBlock=maxItemsInBlock=3,词典和索引的逻辑图如下:
    在这里插入图片描述
图 1
  • 图 1中的的节点有两种,直接指向磁盘的是Term Entry,其余的是Block Entry。每个Entry分4部分,分别是prefix,isLeafNode,isFloorNode,磁盘的偏移量。
  • Entry的L标签代表这个Entry是Leaf-Entry,F标签代表这个Entry是Floor-Entry。Leaf-Entry中没有sub-entry,其存储格式跟non-Leaf-Entry不一样。
  • Floor-Entry表示相同前缀的entry的可以存多个block,上图中前缀te的term有4个,而maxItemsInBlock为3,所以存成了两个block,Floor-Entry的prefix会多存一个字符,te的后面多了an,这是个小的优化。
  • 每个non-Leaf-Entry的FST(index)是由所有sub-entry的FST联合组成的FST,Leaf-Entry的FST则是由Entry的prefix构建的FST
  • prefix构建的FST的output存的是Entry的磁盘偏移量。root block的FST可以遍历到所有block的物理位置。
  • 上图中root block的FST就是由reteteaten四个FST联合而成,其对应的output就是这四个节点的磁盘偏移量。
  • 了解FST的存储格式可以看下这两篇:Lucene源码分析 - FST-BuilderLucene源码分析 - FST

1 源码分析

   write函数将segment中的非IndexOptions.NONE的Field的构建term索引。

  public void write(Fields fields) throws IOException {
   
    String lastField = null;
    for(String field : fields) {
   	// 遍历需要建立索引的field
      lastField = field;

      Terms terms = fields.terms(field);
      if (terms == null) {
   
        continue;
      }

      TermsEnum termsEnum = terms.iterator();	// 获取field的term迭代器
      TermsWriter termsWriter = new TermsWriter(fieldInfos.fieldInfo(field));	// 构建索引的主要类 TermsWriter
      while (true) {
   
        BytesRef term = termsEnum.next();

        if (term == null) {
   
          break;
        }

        termsWriter.write(term, termsEnum);		// 对term 构建词典和索引
      }

      termsWriter.finish();		// 完成field 的构建

    }
  }

   TermsWriterwrite函数会将term的倒排表写入磁盘,pending是待索引列表,pushTerm函数判断pending是否满足构建索引的条件,并将当前term加入pending末尾。

    public void write(BytesRef text, TermsEnum termsEnum) throws IOException {
   
	  // 将term的倒排表写入磁盘,返回磁盘偏移量
      BlockTermState state = postingsWriter.writeTerm(text, termsEnum, docsSeen);	
      if (state != null) {
   

        pushTerm(text);		// 待索引列表构建索引
       
        PendingTerm term = new PendingTerm(text, state);
        pending.add(term);		//当前term加入待索引列表

        sumDocFreq += state.docFreq;
        sumTotalTermFreq += state.totalTermFreq;
        numTerms++;
        if (firstPendingTerm == null) {
   
          firstPendingTerm = term;
        }
        lastPendingTerm = term;
      }
    }

   pending列表类似于栈,因为后续代码对pengding的读写都是在列表尾部进行的。pending栈中的元素可以是PendingTerm(term)和PendingBlock(block),后文用Entry表示。
   函数pushTerm会判断从栈顶为起始位置,至少连续minItemsInBlock个Entry集合中,这个Entry集合的公共前缀长度大于pos,如果满足条件,那么将这个集合以block的格式写入词典和索引文件中,其中pos等于当前Entry与前一个Entry的公共前缀长度。
   prefixStarts数组存的Entry在pending中的位置,prefixStarts[K]=Id表示从栈的第Id个term到栈顶,这个Entry集合的公共前缀长度为K。
   以输入为["teacher", "team", "tend"]为例,先输入teacher
在这里插入图片描述
   再输入team
在这里插入图片描述
   再输入tend
在这里插入图片描述

   这里有几个细节值得注意:

  • 第一,Entry集合的数量只要超过minItemsInBlock就写到磁盘,却并没有限制Entry数量的上限,这是因为writeBlocks函数会把集合分多个block写。
  • 第二,上图的prefixStarts总长度没变化,画出来的部分是有效长度,就是栈顶Entry的长度,因为下一个term和栈顶的term的公共前缀最长也只能是栈顶Entry的长度。
  • 第三,writeBlocks函数写的Entry集合的的公共前缀长度范围是[pos, lastTerm.length()-1],而不是[pos-1, lastTerm.length()-1],这里是因为下一个pushTerm的前缀有可能是te,需要等所有te前缀的term写入pending
  • 第四,teacherteam的公共前缀有[t,te,tea],但是如果这两个写入block,但是需要优先把最长的前缀长度tea的Entry写入磁盘。

   具体代码逻辑如下:

    private void pushTerm(BytesRef text) throws IOException {
   
      int limit = Math.min(lastTerm.length(), text.length);

      // 计算当前term与前一个term的公共前缀长度:
      int pos = 0;
      while (pos < limit && lastTerm.byteAt(pos) == text.bytes[text.offset+pos]) {
   
        pos++;
      }

      // 对公共前缀长度超过pos,且数量大于minItemsInBlock的term集合写到一个或者多个block
      // 反向遍历,让公共前缀长度比较大的term集合优先写入block
      for(int i=lastTerm.length()-1;i>=pos;i--) {
   

		// 计算与栈顶的Entry的公共前缀为 i 的Entry的数量
        int prefixTopSize = pending.size() - prefixStarts[i];
        if (prefixTopSize >= minItemsInBlock) {
   
          writeBlocks(i+1
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值