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源代码解析与详解 #### 一、帧差法基本原理及应用背景 帧差法是一种广泛应用于视频处理领域的技术,主要用于运动目标检测。其核心思想是通过比较相邻帧之间的差异来判断物体的移动情况。这种...
Vis5D,全称为“Visualize Five Dimensions”,是一种开放源代码的三维可视化软件,用于展示四维(3D空间+时间)气象和海洋数据。通过将FST数据转换为Vis5D格式,用户可以利用Vis5D的强大功能来创建动态、交互式的4D...
通过查看源代码,用户可以深入理解库的内部实现,包括如何与底层FST库交互,以及如何定义和实现各种功能。 总的来说,`FstFileFormat.jl` 提供了Julia用户处理FST文件的强大工具,促进了语言处理任务的高效开发和...
1. **读取源代码**:程序首先读取源代码文件,逐字符进行处理。 2. **分隔符识别**:根据C++语言的语法规则,识别出关键字、标识符、常量、运算符、注释等不同类型的符号。例如,"int"是关键字,"myVariable"是...
`src`目录则可能包含了库的源代码,这对于学习库的工作原理和进行定制修改非常有用。 FST库的优势在于: - 性能:通过高效的编码算法和字节码操作,FST可以在大多数情况下提供比原生序列化更快的速度。 - 小型化:...
4. 学习和研究不同库的源代码。 然而,值得注意的是,虽然这个资源包可能包含很多jar包,但并非所有最新的或者特定版本的库都会存在,因为这取决于创建此资源包时的Maven中央仓库状况。因此,对于最新的开发需求,...
FST在Lucene中的实现涉及复杂的源代码逻辑。了解其读写过程有助于更好地理解Lucene的工作机制。 - **读取**:根据关键字查询FST,找到对应的文档ID列表。 - **写入**:将新的关键字及其文档ID列表添加到FST中,可能...
这款软件的核心优势在于其开放源代码,允许开发者深入研究和定制化,以满足特定的应用场景需求。 在描述中提到,如果Funasr在运行时遇到问题,可能是因为缺少VC运行时库。VC_redist.x64(2022).exe是一个微软的...
如果你要使用这些文件,首先需要解压,然后在Delphi环境中导入并编译相关源代码,理解其工作原理,以便根据自己的需求进行定制。 总的来说,文件克隆在Delphi中是一个相对简单但实用的操作,通过标准库或第三方库...
在openfstwin-1.3.4-master这个压缩包中,你应该能找到源代码、编译好的库文件、头文件以及可能的示例程序和文档。为了在Windows环境下使用OpenFST,你需要先将其正确地编译和安装,然后就可以通过链接到库文件来...
作为开源软件,PyPedal的源代码公开透明,允许用户自由查看、修改和分发。这不仅保证了软件的可持续发展,也鼓励社区成员贡献代码、报告问题和提出改进。开源性质使得PyPedal能够快速响应用户需求,不断优化和扩展...
在提供的压缩包文件`scikit-allel-master`中,包含了scikit-allel项目的源代码。用户可以解压后查看代码,了解其实现原理,或者根据自己的需求进行二次开发。通过安装和导入这个库,生物信息学领域的研究者和开发者...
Erasable Programmable Read-Only Memory的缩写EPROM,是一种非易失性存储器,数据可以通过紫外线照射来擦除,然后重新编程,广泛用于存储固件和软件代码。 ### 电可擦可编程只读存储器(EEPROM) Electrically ...