Lucene源码分析 - FST-Builder

本文深入剖析Lucene FST的构建过程,包括term如何写入FST、共享后缀的实现、output压缩方法以及nextFinalOutput的作用。通过对Builder类的源码分析,解释了FST如何利用Arc和Node进行存储,并详细阐述了每个步骤的关键点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文的代码以lucene-core 6.3.0为准,包含Builder的add函数,output的压缩,finish函数等整个类所有代码的解析。

0 基本信息

   分享一下Builder类的源码解析。Builder类用来构建FST,以输入串(后文用term表示)acdxazyx为例,得到的FST如下图:
在这里插入图片描述

图 1

   上图可以看到FST可以共享前缀和后缀,term的一个字节单元对应FST的一个Arc。先了解一下Builder中的一些基本信息。

  • 从Builder类的开头注释中看到,输入的term需要先排好序。
  • 在Builder类中,用类Arc代表上图的边,用类UnCompiledNode代表上图的节点。
  • NodeHash就是个Node的hash表,在共享后缀Node的时候,如果两个Node的hash值一致,那就只写一次到FST。
  • 仔细看下Arc和UnCompiledNode这两个类,会发现都有outputisFinal两个字段,Arc的output,表示当前Arc上有输出,isFinal表示Arc已经是终止Arc,其target是-1,不可以继续深度遍历。Node的isFinal表示从Node出发的Arc数量是0,当Node是Final Node时,output才有值。
  • 图1中,Node的编号仅代表其写入FST的顺序, 每个Node的Arc会写入FST,Node写入FST只有编号或者Address,想了解FST的源码可以看下我的另一篇Lucene源码分析 - FST
  • 对于Arc数量为0的Node,如图 1中的Node 1,不会写入FST。
  • Arc的target是Final Node的是,nextFinalOutput等于target的output,这样做的作用后文会提到。

1 源码分析

1.1 term 如何写入 FST

   Builder的add函数接收term并写入FST,首先判断输入的term是否为空,然后计算当前term与上个term的公共前缀长度得到prefixLenPlus1:

	// 判断输入的term是否为空
    if (input.length == 0) {
   
      frontier[0].inputCount++;
      frontier[0].isFinal = true;
      fst.setEmptyOutput(output);
      return;
    }

    int pos1 = 0;
    int pos2 = input.offset;
    final int pos1Stop = Math.min(lastInput.length(), input.length);
    // 遍历当前term与上个term的公共前缀的长度
    while(true) {
   
      // 共享Node的term的数量+1
      frontier[pos1].inputCount++;
      if (pos1 >= pos1Stop || lastInput.intAt(pos1) != input.ints[pos2]) {
   
        break;
      }
      pos1++;
      pos2++;
    }
    final int prefixLenPlus1 = pos1+1;
    // 将上个term的后缀写入FST
	freezeTail(prefixLenPlus1);

   这里的frontier是个UnCompiledNode数组,主要是存公共前缀Node,Node的inputCount是指共享这个Node的term的数量(后文用Node的共享数代替),根节点被所有的term共享。已知了公共前缀的长度,之后通过freezeTail将上个term的后缀Node全部写入到FST中或者删除掉。
   Builder设置变量minSuffixCount1minSuffixCount2来控制Node的删除,Node的共享数小于minSuffixCount1的节点删除,或者Node的父节点的共享数小于minSuffixCount2的节点删除掉。这里删掉节点的逻辑似乎没有用到,Lucene的其他代码中,minSuffixCount1minSuffixCount2都设置成了0。后文中,不再考虑删除节点的情况,也就是认为minSuffixCount1minSuffixCount2都是0。

  private void freezeTail(int prefixLenPlus1) throws IOException {
   
    //这里downTo大于等于1可以保证根节点不会被写入到FST中去,根节点必须要所有节点写完之后才能写到FST
    final int downTo = Math.max(1, prefixLenPlus1);
    // 这里从后往前遍历 
    // 1. 里面有删除节点的逻辑
    // 2. 所有节点写入FST之后,是深度优先遍历的序列
    for(int idx=lastInput.length(); idx >= downTo; idx--) {
   
	  
      boolean doPrune = false;
      boolean doCompile = false;

      final UnCompiledNode<T> node = frontier[idx];
      final UnCompiledNode<T
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值