本篇博客介绍FST的生成。它使用的是生成器模式,因为FST的生成太复杂了,所以必须使用生成器模式,他的类是:org.apache.lucene.util.fst.Builder.Builder(INPUT_TYPE, int, int, boolean, boolean, int, Outputs, boolean, float, boolean, int),代码如下:
/**
inputType 表示的是输入的类型,FST规定只能输入数字类型,字符串的输入也要转化为数字,这里是判断字符串的范围,不同的INPUT_TYPE有不同的范围
minSuffixCount1:这个说的是穿越某个节点的边的数量的下限,如果小于这个值,则要删除这个节点,在lucene中是0,所以永远不会删除,不用管这个参数就可以。
minSuffixCount2:不用管这个参数,是0,在代码中不起作用
doShareSuffix:最后编译节点进入FST的时候,要不要共享后缀,
doShareNonSingletonNodes:当编译某一个节点的时候,如果这个节点是多个term都穿过的,是否要共享此节点,如果不共享,则直接编译入fst中,否则要放入一个去重的对象中,让其他的节点共享这个节点。
shareMaxTailLength:当编译进fst时要共享后缀的额时候,最多共享多少个
outputs:输出的类型
doPackFST:对最终生成的FST,要不要继续压缩(后面会专门将压缩的过程)
acceptableOverheadRatio:在使用packedInts的时候使用的参数(参见我描述packedInts的博客:https://www.iteye.com/blog/suichangkele-2427364 有多篇,不一一列出)
allowArrayArcs:在存储一个节点的多个发出的arc的时候,对于某一个arc的存储,是否可以使用fixedArray的格式,后面会有介绍
bytesPageBits:在使用BytesStore对象记录fst编译后的二进制内容时,使用的byte[]的大小
*/
public Builder(FST.INPUT_TYPE inputType, int minSuffixCount1, int minSuffixCount2, boolean doShareSuffix,
boolean doShareNonSingletonNodes, int shareMaxTailLength, Outputs outputs, boolean doPackFST,
float acceptableOverheadRatio, boolean allowArrayArcs, int bytesPageBits) {
this.minSuffixCount1 = minSuffixCount1;
this.minSuffixCount2 = minSuffixCount2;
this.doShareNonSingletonNodes = doShareNonSingletonNodes;
this.shareMaxTailLength = shareMaxTailLength;
this.doPackFST = doPackFST;
this.acceptableOverheadRatio = acceptableOverheadRatio;
this.allowArrayArcs = allowArrayArcs;
fst = new FST<>(inputType, outputs, doPackFST, acceptableOverheadRatio, bytesPageBits);//生成一个fst,
bytes = fst.bytes;
assert bytes != null;
if (doShareSuffix) {//这个就是共享后缀的部分,如果是的话,需要一个单独的对象,用来查找共享后缀,这个对象后面有介绍。
dedupHash = new NodeHash<>(fst, bytes.getReverseReader(false));
} else {
dedupHash = null;
}
NO_OUTPUT = outputs.getNoOutput();//对于没有输出的arc,统一通这个作为输出
@SuppressWarnings({"unchecked" })
final UnCompiledNode[] f = (UnCompiledNode[]) new UnCompiledNode[10];//frontier的数组
frontier = f;
for (int idx = 0; idx < frontier.length; idx++) {
frontier[idx] = new UnCompiledNode<>(this, idx);
}
}
上面提到了UnCompiledNode这个类,这个类就是构成Frontier的最小单位,在,用于表示没有编译进FST的节点类,看一下这个类:
/** 还没有写入FST的term的节点,保存一个出现的但是还没有写入FST的值
Expert: holds a pending (seen but not yet serialized) Node. */
public static final class UnCompiledNode implements Node {
final Builder owner;
/** 发出的arc边的数量,一个节点上可能有多个arc,如果是最后一个Node,则是0 */
public int numArcs;
/** 由这个节点出发的所有的arc */
public Arc[] arcs;
// TODO: instead of recording isFinal/output on the node, maybe we should use -1 arc to mean "end" (like we do when reading the FST). Would simplify much code here...
/** 节点的输出,有时候节点也是有输出的,就在下面的prependOutput方法中,其实就是以后的finalOutput,以后会有介绍 */
public T output;
/** 到这个节点截止是不是一个完整的term,如果这个节点是final的,则其可能含有output,后面会解释 */
public boolean isFinal;
/** 有多少个线路走过这个节点,无论是不是最后一个,都算是经过,第一个node的这个属性表示一共多少个term */
public long inputCount;
/** 节点在term中的偏移量(下标),第一个是不用的,是root
This node's depth, starting from the automaton root. */
public final int depth;
/** @param depth The node's depth starting from the automaton root. Needed for LUCENE-2934 (node expansion based on conditions other than the fanout size). */
@SuppressWarnings({"unchecked" })
public UnCompiledNode(Builder owner, int depth) {
this.owner = owner;
arcs = (Arc[]) new Arc[1];
arcs[0] = new Arc<>();
output = owner.NO_OUTPUT;
this.depth = depth;
}
@Override
public boolean isCompiled() {
return false;
}
/** 清除,当把这个节点编译进fst之后调用,因为他已经进入FST了,所以留着也没用了。 */
public void clear() {
numArcs = 0;
isFinal = false;
output = owner.NO_OUTPUT;
inputCount = 0;
// We don't clear the depth here because it never changes for nodes on the frontier (even when reused).
}
/** 获得最后添加的一个arc的输出值 */
public T getLastOutput(int labelToMatch) {
assert numArcs > 0;
assert arcs[numArcs - 1].label == labelToMatch;
return arcs[numArcs - 1].output;
}
/**
* 添加一个arc,到下一个节点,此时并没有添加输出,输出暂定为没有输出,并且设置为不是final
* @param labelarc上的值(不是输出)
* @param target 链接的下一个节点
*/
public void addArc(int label, Node target) {
assert label >= 0;
<