`
suichangkele
  • 浏览: 200307 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

FST源代码解读6——FST的读取

阅读更多

lucene fst 源代码 解读

 

在lucene的官方文档里,有读取的代码的介绍,直接用了一个Util类,代码如下,方法名字是:org.apache.lucene.util.fst.Util.get(FST<T>, IntsRef)

/** Looks up the output for this input, or null if the input is not accepted. */
public static <T> T get(FST<T> fst, IntsRef input) throws IOException {
	// TODO: would be nice not to alloc this on every lookup
	final FST.Arc<T> arc = fst.getFirstArc(new FST.Arc<T>());// 获得一个指向root的虚拟arc
	final BytesReader fstReader = fst.getBytesReader();//获得一个reader,如果是没有压缩的,则是从后面读,否则从前面开始读
	// Accumulate output as we go
	T output = fst.outputs.getNoOutput();
	for (int i = 0; i < input.length; i++) {
		if (fst.findTargetArc(input.ints[input.offset + i], arc, arc, fstReader) == null) {// 获得一个lebal是指定值的arc,如果这个找不到,则认为要查找的input是不存在的,直接返回null,
			return null;
		}
		output = fst.outputs.add(output, arc.output);//找到了,累加输出
	}
	if (arc.isFinal()) {//都找到了,需要判断原来的输入是不是final的,如果不是也不存在。
		return fst.outputs.add(output, arc.nextFinalOutput);//是final的,再累加final输出
	} else {
		return null;
	}
}

 

/**
 * 读的时候调用,找到一个lebal是指定lebal的arc <br/>
 * Finds an arc leaving the incoming arc, replacing the arc in place. This returns null if the arc was not found, else the incoming arc.
 * @param follow 查找的条件的arc
 * @param arc 最终将查询的结果写入到这个里面来
 */
private Arc<T> findTargetArc(int labelToMatch, Arc<T> follow, Arc<T> arc, BytesReader in, boolean useRootArcCache) throws IOException {

	if (labelToMatch == END_LABEL) {//TODO 这个没有用到过,忽略
		if (follow.isFinal()) {
			if (follow.target <= 0) {
				arc.flags = BIT_LAST_ARC;
			} else {
				arc.flags = 0;
				// NOTE: nextArc is a node (not an address!) in this case:
				arc.nextArc = follow.target;
				arc.node = follow.target;
			}
			arc.output = follow.nextFinalOutput;
			arc.label = END_LABEL;
			return arc;
		} else {
			return null;
		}
	}

	// Short-circuit if this arc is in the root arc cache:
	if (useRootArcCache && cachedRootArcs != null && follow.target == startNode	&& labelToMatch < cachedRootArcs.length) {//从缓存中查找
		final Arc<T> result = cachedRootArcs[labelToMatch];
		// LUCENE-5152: detect tricky cases where caller modified previously returned cached root-arcs:
		assert assertRootCachedArc(labelToMatch, result);
		if (result == null) {
			return null;
		} else {
			arc.copyFrom(result);
			return arc;
		}
	}
	
	if (!targetHasArcs(follow)) {//判断指向的节点是否存在符合条件的arc
		return null;
	}
	in.setPosition(getNodeAddress(follow.target));//准备从bytes中读取,提前找好位置,定位到taget节点的开始的位置
	arc.node = follow.target;

	if (in.readByte() == ARCS_AS_FIXED_ARRAY) {//fixedArray格式的,采用二分查找
		// Arcs are full array; do binary search:
		arc.numArcs = in.readVInt();//arc的数量
		if (packed || version >= VERSION_VINT_TARGET) {//一个arc占用的内存的大小
			arc.bytesPerArc = in.readVInt();
		} else {
			arc.bytesPerArc = in.readInt();
		}
		arc.posArcsStart = in.getPosition();//这个节点中的arc开始的位置
		int low = 0;//开始用二分法查找
		int high = arc.numArcs - 1;
		while (low <= high) {
			int mid = (low + high) >>> 1;
			in.setPosition(arc.posArcsStart);
			in.skipBytes(arc.bytesPerArc * mid + 1);//先指向开始,然后再根据每个arc的大小和是第几个arc移动指针,这样就移动到了中间的一个arc上
			int midLabel = readLabel(in);//读取这个arc的lebal
			final int cmp = midLabel - labelToMatch;//根据label的大小判断该怎么移动指针。
			if (cmp < 0) {
				low = mid + 1;
			} else if (cmp > 0) {
				high = mid - 1;
			} else {
				arc.arcIdx = mid - 1;//正好匹配,则读取这个arc。这里减一是因为后面再readNextRealArc中会自动加一
				return readNextRealArc(arc, in);
			}
		}
		return null;
	}

	// Linear scan  如果不是fixedArray的,则先读取第一个,读取到arc中
	readFirstRealTargetArc(follow.target, arc, in);

	while (true) {//遍历
		// TODO: we should fix this code to not have to create object for the output of every arc we scan... only for the matching arc, if found
		if (arc.label == labelToMatch) {//相同的
			return arc;
		} else if (arc.label > labelToMatch) {//因为是从小到大的排列每个节点上的arc,所以如果一个arc的值大于目标值则标识找不到
			return null;
		} else if (arc.isLast()) {//找不到
			return null;
		} else {//继续读取下一个arc的属性到arc中
			readNextRealArc(arc, in);
		}
	}
}

 

还有一个功能是根据输出查找输入,即:Util.getByOutput(FST<Long>, long),不过他是有条件的,只有当加入到fst的key-value中所有的value是按照从小到大的顺序添加时才可以使用这个方法,且只有当输出值是long时。这种的使用情况为:用在文件指针上。代码如下:

/**
 * Reverse lookup (lookup by output instead of by input), in the special case when your FSTs outputs are strictly ascending. This locates the input/output pair where the output is equal to the target, and will return null if that output does not exist.
 * <p>
 * NOTE: this only works with {@code FST<Long>}, only works when the outputs are ascending in order with the inputs. For example, simple ordinals (0,1, 2, ...), or file offets (when appending to a file) fit this.  只有long的输出才可以用。
 */
public static IntsRef getByOutput(FST<Long> fst, long targetOutput) throws IOException {
	
	final BytesReader in = fst.getBytesReader();

	// TODO: would be nice not to alloc this on every lookup  指向root的虚拟arc
	FST.Arc<Long> arc = fst.getFirstArc(new FST.Arc<Long>());

	FST.Arc<Long> scratchArc = new FST.Arc<>();

	final IntsRefBuilder result = new IntsRefBuilder();
	return getByOutput(fst, targetOutput, in, arc, scratchArc, result);//
}

/**
 * 别忘了前提是按照output严格递增的顺序来添加的,这个特性导致arc上的输出也是不会减小的,而且不会出现负数的输出。在读取的时候如果一个节点上任何arc的输出值和之前读取的所有的arc上的输出的累加和
 * 不等于targetOutput,则此节点上选择的arc是  输出值和之前所有的arc的输出值的和比targetOutput要小的集合中的最大的一个,解释:首先不能是超过targetOutput的,因为这种fst的所有的arc的输出都不会有负数,
 * 所以一旦超过targetOutput,则不会变小;第二:因为所有的输出是按照严格递增来的,那么同一个节点上发出的arc中,顺序靠后的arc上的输出一定比   在这个arc之前的任意arc开始到结束节点的所有arc上的输出的和要大
 * ,比如在最大的一个都不够大的时候,必须选最大的一个才有可能是相等的,不选最大的那个则一定是比targetOutput小的。
 * @param fst
 * @param targetOutput	查找的目标值,也即是输出
 * @param in			读取fst的reader
 * @param arc			指向root的虚拟的arc
 * @param scratchArc	        scratchArc是一个复用的对象,用来记录上一个读取的arc
 * @param result		将查找到的结果写入到里面
 * @return
 * @throws IOException
 */
public static IntsRef getByOutput(FST<Long> fst, long targetOutput, BytesReader in, Arc<Long> arc, Arc<Long> scratchArc, IntsRefBuilder result) throws IOException {
	
	long output = arc.output;//读取的lebal
	int upto = 0;

	while (true) {
		
		// 判断之前读取的输出是否匹配targetOutput
		if (arc.isFinal()) {
			final long finalOutput = output + arc.nextFinalOutput;//留心final output
			if (finalOutput == targetOutput) {//如果相等,则找到了
				result.setLength(upto);
				return result.get();
			} else if (finalOutput > targetOutput) {
				return null;
			}
		}//如果不是final的,则继续读取当前的arc的指向的节点的所有的arc

		if (FST.targetHasArcs(arc)) {
			result.grow(1 + upto);//还没有查找到,而且有新的查找可能,所以要增大

			fst.readFirstRealTargetArc(arc.target, arc, in);//读取arc.target节点上的第一个arc,将结果读取再arc上

			if (arc.bytesPerArc != 0) {//这个节点是采用fixedArray格式存储的

				int low = 0;
				int high = arc.numArcs - 1;
				int mid = 0;
				boolean exact = false;
				while (low <= high) {//在所有的arc中查找;如果找不到的话
					mid = (low + high) >>> 1;
					in.setPosition(arc.posArcsStart);// 指向这个节点的开始
					in.skipBytes(arc.bytesPerArc * mid);//移动到这个arc的开始
					final byte flags = in.readByte();//读取这个arc的flag
					fst.readLabel(in);//读取label
					final long minArcOutput;
					if ((flags & FST.BIT_ARC_HAS_OUTPUT) != 0) {//有输出
						final long arcOutput = fst.outputs.read(in);//读取输出
						minArcOutput = output + arcOutput;//当前查找到的输出
					} else {
						minArcOutput = output;
					}
					if (minArcOutput == targetOutput) {
						exact = true;
						break;
					} else if (minArcOutput < targetOutput) {
						low = mid + 1;
					} else {
						high = mid - 1;
					}
				}

				if (high == -1) {// 这个是连第一个都比targetOutput大的时候,此时说明找不到,因为后面的只能更大
					return null;
				} else if (exact) {//他的目的是读取下标是mid的arc,但是因为下面的readNextRealArc会加一,所以这里要减一
					arc.arcIdx = mid - 1;
				} else {// 这个需要好好分析一下二分查找,如果找不到的话,low永远是在准确值的左边的一个,所以减一就可以了,因为下面的readNextRealArc中会加一,所以这里要减二。
					arc.arcIdx = low - 2;
				}

				fst.readNextRealArc(arc, in);// 读取下一个arc(读了之后可能是准确的,可能是不准确的,如果不准确,则是输出最紧接于targetOutput的arc)
				result.setIntAt(upto++, arc.label);//将读取的lebal写在result中
				output += arc.output;//将读取的输出添加到output

			} else {//当不是fixedArray格式存储的时候,挨个比对,要查找output+arc.ouput < targetOutput中最大的一个(或者说最后的一个),也就是下面的pervArc,然后再去继续读取他指向的node上的arc
				FST.Arc<Long> prevArc = null;
				while (true) {//遍历当前节点上的所有的arc
					// This is the min output we'd hit if we follow this arc:
					final long minArcOutput = output + arc.output;//从小的开始匹配
					if (minArcOutput == targetOutput) {//相等了,不能再对比下去了,因为后面的一定更大。所以要退出让前面的程序判断是否满足条件,即是否是final的
						// Recurse on this arc:
						output = minArcOutput;
						result.setIntAt(upto++, arc.label);
						break;
					} else if (minArcOutput > targetOutput) {//我们的目的就是找到一个比targetOutput大的,然后确定他的前一个,也就是prevArc。
						if (prevArc == null) {//prevArc不存在,说明是第一次遍历这个node,当前的minArcOutput是这个节点任意的arc所能提供的最小的值,最小的值都比targetOutput,则一定不满足
							return null;
						} else {//优先看最后一个else,就很好明白了
							// Recurse on previous arc:
							arc.copyFrom(prevArc);
							result.setIntAt(upto++, arc.label);
							output += arc.output;
							break;
						}
					} else if (arc.isLast()) {// 小于targetOutput且是最后一个,则需要继续读取这个arc指向的节点。所以要break,退出对当前节点的遍历。如果不是最后一个,则进入下面的else,继续遍历当前节点的arc
						// Recurse on this arc:
						output = minArcOutput;
						result.setIntAt(upto++, arc.label);
						break;
					} else {// 结果比targetOutput要小,则记录这个arc为prev,因为下一个arc有可能比targetOutput要大,如果是的话,那么prev就是我们要找的arc。
						// Read next arc in this node:
						prevArc = scratchArc;//scratchArc就是当前的arc,传入的时候是一个对象
						prevArc.copyFrom(arc);
						fst.readNextRealArc(arc, in);//继续读取这个节点的下一个arc,然后继续判断这个是否比targetOutput大
					}
				}
			}
		} else {//如果这个节点没有输出,说明是最后一个节点了,还不匹配,则找不到
			return null;
		}
	}
}

 这个的代码不难,他的关键就是在查找一个节点的arc的时候,使用所有 arc输出和之前的输出的和 小于目标值的所有的arc中,和最大的一个arc,然后再顺着这个思路查找这个arc的下一个节点。不过用处我觉得不如根据input查找out多。 

 

如果懂了写的代码,读的代码是很容易理解的,FST就这样看完了,花了我足足一周的时间,不过好开心哈哈哈哈哈哈。如果有地方错误,请联系我的qq:1308567317

 

分享到:
评论

相关推荐

    帧差法改进matlab源代码

    ### 帧差法改进Matlab源代码解析与详解 #### 一、帧差法基本原理及应用背景 帧差法是一种广泛应用于视频处理领域的技术,主要用于运动目标检测。其核心思想是通过比较相邻帧之间的差异来判断物体的移动情况。这种...

    FST to v5d Data Converter-开源

    Vis5D,全称为“Visualize Five Dimensions”,是一种开放源代码的三维可视化软件,用于展示四维(3D空间+时间)气象和海洋数据。通过将FST数据转换为Vis5D格式,用户可以利用Vis5D的强大功能来创建动态、交互式的4D...

    FstFileFormat.jl:fst格式的Julia绑定

    通过查看源代码,用户可以深入理解库的内部实现,包括如何与底层FST库交互,以及如何定义和实现各种功能。 总的来说,`FstFileFormat.jl` 提供了Julia用户处理FST文件的强大工具,促进了语言处理任务的高效开发和...

    词法分析程序(C++源码)

    1. **读取源代码**:程序首先读取源代码文件,逐字符进行处理。 2. **分隔符识别**:根据C++语言的语法规则,识别出关键字、标识符、常量、运算符、注释等不同类型的符号。例如,"int"是关键字,"myVariable"是...

    Java快速序列化库

    `src`目录则可能包含了库的源代码,这对于学习库的工作原理和进行定制修改非常有用。 FST库的优势在于: - 性能:通过高效的编码算法和字节码操作,FST可以在大多数情况下提供比原生序列化更快的速度。 - 小型化:...

    maven本地仓库资源,大部分jar包都有

    4. 学习和研究不同库的源代码。 然而,值得注意的是,虽然这个资源包可能包含很多jar包,但并非所有最新的或者特定版本的库都会存在,因为这取决于创建此资源包时的Maven中央仓库状况。因此,对于最新的开发需求,...

    面试指南-Lucene:ES篇.md

    FST在Lucene中的实现涉及复杂的源代码逻辑。了解其读写过程有助于更好地理解Lucene的工作机制。 - **读取**:根据关键字查询FST,找到对应的文档ID列表。 - **写入**:将新的关键字及其文档ID列表添加到FST中,可能...

    开源语音识别 funasr windows版本二进制包

    这款软件的核心优势在于其开放源代码,允许开发者深入研究和定制化,以满足特定的应用场景需求。 在描述中提到,如果Funasr在运行时遇到问题,可能是因为缺少VC运行时库。VC_redist.x64(2022).exe是一个微软的...

    Cloner files_clonning_

    如果你要使用这些文件,首先需要解压,然后在Delphi环境中导入并编译相关源代码,理解其工作原理,以便根据自己的需求进行定制。 总的来说,文件克隆在Delphi中是一个相对简单但实用的操作,通过标准库或第三方库...

    openfstwin-1.3.4:OpenFST 1.3.x分支的更新版本(以Paul Dixon在1.3.3上的工作为基础)

    在openfstwin-1.3.4-master这个压缩包中,你应该能找到源代码、编译好的库文件、头文件以及可能的示例程序和文档。为了在Windows环境下使用OpenFST,你需要先将其正确地编译和安装,然后就可以通过链接到库文件来...

    PyPedal-开源

    作为开源软件,PyPedal的源代码公开透明,允许用户自由查看、修改和分发。这不仅保证了软件的可持续发展,也鼓励社区成员贡献代码、报告问题和提出改进。开源性质使得PyPedal能够快速响应用户需求,不断优化和扩展...

    scikit-allel:用于探索和分析遗传变异数据的Python软件包

    在提供的压缩包文件`scikit-allel-master`中,包含了scikit-allel项目的源代码。用户可以解压后查看代码,了解其实现原理,或者根据自己的需求进行二次开发。通过安装和导入这个库,生物信息学领域的研究者和开发者...

    电子类常用缩写对照(PDF)

    Erasable Programmable Read-Only Memory的缩写EPROM,是一种非易失性存储器,数据可以通过紫外线照射来擦除,然后重新编程,广泛用于存储固件和软件代码。 ### 电可擦可编程只读存储器(EEPROM) Electrically ...

Global site tag (gtag.js) - Google Analytics