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

FST源代码解读3——编译节点

阅读更多

上一篇看完了FST.Builder的代码了,这里写FST代码了。先看编译节点时是如何操作的,即org.apache.lucene.util.fst.FST.addNode(Builder<T>, UnCompiledNode<T>)方法:

long addNode(Builder<T> builder, Builder.UnCompiledNode<T> nodeIn) throws IOException {
	T NO_OUTPUT = outputs.getNoOutput();

	if (nodeIn.numArcs == 0) {// 如果添加的是没有arc的,也就是最后一个,返回的都是一样的,这样就可以认为所有的结束节点是同一个节点。
		if (nodeIn.isFinal) {
			return FINAL_END_NODE;
		} else {
			return NON_FINAL_END_NODE;// 不知道为啥还有这种情况,没有arc却不是final的
		}
	}
	// 往下走都是有arc的
	final long startAddress = builder.bytes.getPosition();// 写这个节点的开始的position,这里的bytes是保存编译后的二进制的byte[]的,还没有详细的介绍,
	// 怎么存储这个node的arc,如果有太多的,则使用fixedArray的格式,每个arc用相同的空间,因为arc上的label一定是按照顺序排序的,这样能用二分查找
	final boolean doFixedArray = shouldExpand(builder, nodeIn);
	if (doFixedArray) {
		if (builder.reusedBytesPerArc.length < nodeIn.numArcs) {// 将用来记录每个arc占用多少内存的数组扩容
			builder.reusedBytesPerArc = new int[ArrayUtil.oversize(nodeIn.numArcs, 1)];
		}
	}

	builder.arcCount += nodeIn.numArcs;// 计算arc的数量

	final int lastArc = nodeIn.numArcs - 1;//

	long lastArcStart = builder.bytes.getPosition();// 记录一个arc写入的开始
	int maxBytesPerArc = 0;// 所有的arc中,最大的一个arc使用的byte[]的大小
	for (int arcIdx = 0; arcIdx < nodeIn.numArcs; arcIdx++) { // 遍历这个节点所有的arc,将每个arc写入到byteStore中去,记录每个arc使用的byte[],记录最大的arc使用的byte[],

		final Builder.Arc<T> arc = nodeIn.arcs[arcIdx];// arc
		final Builder.CompiledNode target = (Builder.CompiledNode) arc.target;// 这个arc指向的节点
		int flags = 0;

		if (arcIdx == lastArc) {// 是否是该节点的最后一个arc
			flags += BIT_LAST_ARC;
		}

		if (builder.lastFrozenNode == target.node && !doFixedArray) {// 刚刚写入到byte[]中的节点就是下一个节点,此时不用存储target,两个节点的node是有规律的,node值差一,根据这个node就可以计算出下一个node,然后就可以获得位置;否则该ARC需要存储target信息
			// TODO: for better perf (but more RAM used) we could avoid this
			// except when arc is "near" the last arc:
			flags += BIT_TARGET_NEXT;// bytes中的下一个Node就是此边的目标节点
		}

		if (arc.isFinal) {
			flags += BIT_FINAL_ARC;
			if (arc.nextFinalOutput != NO_OUTPUT) {// 只有是final的才会有final
				flags += BIT_ARC_HAS_FINAL_OUTPUT;
			}
		} else {
			assert arc.nextFinalOutput == NO_OUTPUT;
		}

		boolean targetHasArcs = target.node > 0;// 大于0说明不是最后一个node

		if (!targetHasArcs) {// 这个arc指向的node没有arc,说明是最后一个arc
			flags += BIT_STOP_NODE;
		} else if (inCounts != null) {// 不是最后一个arc
			inCounts.set((int) target.node, inCounts.get((int) target.node) + 1);//增加指向这个节点的arc的数量
		}

		if (arc.output != NO_OUTPUT) {
			flags += BIT_ARC_HAS_OUTPUT;
		}

		builder.bytes.writeByte((byte) flags);
		writeLabel(builder.bytes, arc.label);// 写入标记为,表示这个arc都是存储了什么,等读的时候解析用

		if (arc.output != NO_OUTPUT) {//
			outputs.write(arc.output, builder.bytes);// 将输出写入到builder.bytes中
		}

		if (arc.nextFinalOutput != NO_OUTPUT) {// 将finalOutput写入
			outputs.writeFinalOutput(arc.nextFinalOutput, builder.bytes);
		}

		if (targetHasArcs && (flags & BIT_TARGET_NEXT) == 0) {// 写入目标节点的编号。第一个targetHasArcs说明这个arc的目标节点不是最后node,(flags & BIT_TARGET_NEXT) == 0 表示他的目标节点不是紧挨着的下一个节点,此时需要单独记录下一个节点的编号
			assert target.node > 0;
			builder.bytes.writeVLong(target.node);
		}

		// just write the arcs "like normal" on first pass, but record how many bytes each one took, and max byte size:
		if (doFixedArray) {//如果是使用fixedArray格式的话,需要记录每个arc使用的内存大小
			builder.reusedBytesPerArc[arcIdx] = (int) (builder.bytes.getPosition() - lastArcStart);
			lastArcStart = builder.bytes.getPosition();
			maxBytesPerArc = Math.max(maxBytesPerArc, builder.reusedBytesPerArc[arcIdx]);//计算arc占用内存最大的值
		}
	}

	if (doFixedArray) {// 按照固定的长度重新写一遍,每个arc都占用maxBytesPerArc个byte,覆盖之前写入的arc。写之前先写入header
		final int MAX_HEADER_SIZE = 11; // header(byte) + numArcs(vint) + numBytes(vint)
		assert maxBytesPerArc > 0;
		// 2nd pass just "expands" all arcs to take up a fixed byte size

		// create the header
		// TODO: clean this up: or just rewind+reuse and deal with it
		byte header[] = new byte[MAX_HEADER_SIZE];
		ByteArrayDataOutput bad = new ByteArrayDataOutput(header);
		// write a "false" first arc:
		bad.writeByte(ARCS_AS_FIXED_ARRAY);// 其实是0,这个是第一个写入的,但是在reverse之后就成了最后一个了。
		bad.writeVInt(nodeIn.numArcs);
		bad.writeVInt(maxBytesPerArc);
		int headerLen = bad.getPosition();

		final long fixedArrayStart = startAddress + headerLen;// 写完header之后的position,也就是写第一个arc的开始

		// expand the arcs in place, backwards
		long srcPos = builder.bytes.getPosition();// 现在不按照fixedArray写的postion,这个是一定小于destPos的,因为destPos是将每个arc按照最大的长度写的。
		long destPos = fixedArrayStart + nodeIn.numArcs * maxBytesPerArc;// 写完所有的arc之后的position
		assert destPos >= srcPos;
		if (destPos > srcPos) { // 一定进入
			builder.bytes.skipBytes((int) (destPos - srcPos));// 先在bytesStore中留出足够的空间。
			for (int arcIdx = nodeIn.numArcs - 1; arcIdx >= 0; arcIdx--) {// 从后往前写每个arc,将之前写的移动到新的位置上
				destPos -= maxBytesPerArc;// 这个arc即将要写入的开始,也就是最后减去n*maxBytesPerArc
				srcPos -= builder.reusedBytesPerArc[arcIdx];// 回退到之前写的这个arc的开始位置,
				if (srcPos != destPos) {// 可能会相等,绝大部分不相等
					assert destPos > srcPos : "destPos=" + destPos + " srcPos=" + srcPos + " arcIdx=" + arcIdx + " maxBytesPerArc=" + maxBytesPerArc + " reusedBytesPerArc[arcIdx]=" + builder.reusedBytesPerArc[arcIdx] + " nodeIn.numArcs=" + nodeIn.numArcs;
					builder.bytes.copyBytes(srcPos, destPos, builder.reusedBytesPerArc[arcIdx]);// 自己copy自己,
				}
			}
		}
		// now write the header 虽然是后写,但是写在arcs之前。
		builder.bytes.writeBytes(startAddress, header, 0, headerLen);
	}

	final long thisNodeAddress = builder.bytes.getPosition() - 1;// 这个节点结束的位置
	// 将刚刚写入的一片byte[]都反转过来,这是因为在调用这个方法的时候,添加的节点的顺序就是倒叙的,从frontier中最后的一个node开始的,生成的编译后的byte[]当然也是倒叙的,
        // 而刚刚添加的这个node的所有的arc都是正序的,所以把这个node的所有的arc都倒叙,这样就统一顺序了,下面再生成一个reader的时候,就可以直接从后面开始顺序读取了。
	builder.bytes.reverse(startAddress, thisNodeAddress);

	// PackedInts uses int as the index, so we cannot handle > 2.1B nodes when packing:
	if (nodeAddress != null && builder.nodeCount == Integer.MAX_VALUE) {
		throw new IllegalStateException("cannot create a packed FST with more than 2.1 billion nodes");
	}

	builder.nodeCount++;
	final long node;
	if (nodeAddress != null) {// 带有压缩的(nodeAddres下面有解释)
		// Nodes are addressed by 1+ord:
		if ((int) builder.nodeCount == nodeAddress.size()) {// 扩容nodeAddress
			nodeAddress = nodeAddress.resize(ArrayUtil.oversize(nodeAddress.size() + 1, nodeAddress.getBitsPerValue()));
			inCounts = inCounts.resize(ArrayUtil.oversize(inCounts.size() + 1, inCounts.getBitsPerValue()));
		}
		nodeAddress.set((int) builder.nodeCount, thisNodeAddress);// 第n个节点结束的位置
		node = builder.nodeCount;//node其实就是加入到fst中的序号,是一个递增的值
	} else {
		node = thisNodeAddress;//如果没有压缩的,则node就是这个节点在byte[]中的开始位置。
	}
	// 通过返回的值可以确定下一个节点
	return node;// 返回的值,如果是压缩的,则返回的是编号(通过编号可以获得这个节点在byteStore中的结束位置),如果不是,则是这个节点在byteStore中的结束位置-1(也就是倒序开始读的位置)
}

上面的方法还是很好理解的,将一个没有编译的节点传入这个参数,如果这个节点是最后一个节点,则永远返回一个小于1的数字,这样所有的结束的节点就可以都看做是同一个。如果不是最后一个节点,将这个节点的多个发出的arc的属性写在BytesStore bytes对象中(这个类下面有介绍),写的时候可能有两种方式,一个是每个arc占固定的大小,一个是可变的,固定大小的好处是可以使用二分法查找,加快查找速度,所以他只有在arc数量很多的时候才会使用;写入的内容包括标记、label、输出、final输出、指向的节点的开始。写完后再reverse过来,方便后面的统一读取,因为未编译的节点写入fst的顺序就是倒叙的,所以这里也要变为倒叙的。写完之后,需要返回这个未编译节点在bytes中的开始位置,这里也有两种情况,一种是直接返回绝对的位置(对应于上面的没有压缩的情况),一种是返回这个节点的序号,然后使用一个单独的对象记录序号到绝对位置的映射(也就是上面的nodeAddress,这个类其实就是封装的packedInts,可以将其理解为一个int[])。frontier中未编译的节点的arc根据这个序号(或者是绝对位置)就能找到他的目标节点在bytes中的开始位置了。

 

BytesStore的介绍:这个其实就是封装了byte[],提供适合FST的方法,代码如下:

class BytesStore extends DataOutput implements Accountable {

	private static final long BASE_RAM_BYTES_USED = RamUsageEstimator.shallowSizeOfInstance(BytesStore.class)
			+ RamUsageEstimator.shallowSizeOfInstance(ArrayList.class);

	/** 先写入current,当current写的到blockSize时,就加入到blocks中,然后创建一个新的 */
	private final List<byte[]> blocks = new ArrayList<>();

	private final int blockSize;
	private final int blockBits;
	private final int blockMask;

	private byte[] current;
	private int nextWrite;

	public BytesStore(int blockBits) {
		this.blockBits = blockBits;
		blockSize = 1 << blockBits;
		blockMask = blockSize - 1;
		nextWrite = blockSize;
	}

	/** Pulls bytes from the provided IndexInput. */
	public BytesStore(DataInput in, long numBytes, int maxBlockSize) throws IOException {
		
		int blockSize = 2;
		int blockBits = 1;
		while (blockSize < numBytes && blockSize < maxBlockSize) {
			blockSize *= 2;
			blockBits++;
		}
		this.blockBits = blockBits;
		this.blockSize = blockSize;
		this.blockMask = blockSize - 1;
		long left = numBytes;
		while (left > 0) {
			final int chunk = (int) Math.min(blockSize, left);
			byte[] block = new byte[chunk];
			in.readBytes(block, 0, block.length);
			blocks.add(block);
			left -= chunk;
		}

		// So .getPosition still works
		nextWrite = blocks.get(blocks.size() - 1).length;
	}

	/**覆盖写,将制定位置的byte设置为某个值 <br/> Absolute write byte; you must ensure dest is &lt; max position written so far. */
	public void writeByte(int dest, byte b) {
		int blockIndex = dest >> blockBits;
		byte[] block = blocks.get(blockIndex);
		block[dest & blockMask] = b;
	}

	/* 追加写 */
	@Override
	public void writeByte(byte b) {
		if (nextWrite == blockSize) {
			current = new byte[blockSize];
			blocks.add(current);
			nextWrite = 0;
		}
		current[nextWrite++] = b;
	}
	
	
	@Override
	public void writeBytes(byte[] b, int offset, int len) {
		while (len > 0) {
			int chunk = blockSize - nextWrite;
			if (len <= chunk) {
				assert b != null;
				assert current != null;
				System.arraycopy(b, offset, current, nextWrite, len);
				nextWrite += len;
				break;
			} else {
				if (chunk > 0) {
					System.arraycopy(b, offset, current, nextWrite, chunk);
					offset += chunk;
					len -= chunk;
				}
				current = new byte[blockSize];
				blocks.add(current);
				nextWrite = 0;
			}
		}
	}
	

	int getBlockBits() {
		return blockBits;
	}

	/** 覆盖写,用b来覆盖,从b的offset开始,写len个 <br/> Absolute writeBytes without changing the current position. Note: this cannot "grow" the bytes, so you must only call it on already written parts. */
	void writeBytes(long dest, byte[] b, int offset, int len) {
		assert dest + len <= getPosition() : "dest=" + dest + " pos=" + getPosition() + " len=" + len;
		final long end = dest + len;//最后一个位置
		int blockIndex = (int) (end >> blockBits);//最后一个位置第几个块上
		int downTo = (int) (end & blockMask);//每个块中写入的最后的位置
		if (downTo == 0) {//如果恰好是整除
			blockIndex--;
			downTo = blockSize;
		}
		byte[] block = blocks.get(blockIndex);//结束的位置
		while (len > 0) {//从最后开始复制的,
			if (len <= downTo) {
				System.arraycopy(b, offset, block, downTo - len, len);
				break;
			} else {
				len -= downTo;
				System.arraycopy(b, offset + len, block, 0, downTo);
				blockIndex--;
				block = blocks.get(blockIndex);
				downTo = blockSize;
			}
		}
	}

	/**
	 * Absolute copy bytes self to self, without changing the position. Note: this cannot "grow" the bytes, so must only call it on already written parts. <br/>
	 * 从src开始复制,复制len个,从dest开始写,自己copy自己
	 */
	public void copyBytes(long src, long dest, int len) {
		
		long end = src + len;

		int blockIndex = (int) (end >> blockBits);// 复制的块的下标
		int downTo = (int) (end & blockMask);// 复制的块的截止的下标
		if (downTo == 0) {
			blockIndex--;
			downTo = blockSize;
		}
		byte[] block = blocks.get(blockIndex);// 要复制的最后一个

		while (len > 0) {
			if (len <= downTo) {//只复制这一个就够了
				writeBytes(dest, block, downTo - len, len);
				break;
			} else {//只复制这一个不够,还要继续复制,则把这一个复制完。
				len -= downTo;
				writeBytes(dest + len, block, 0, downTo);//覆盖写
				blockIndex--;
				block = blocks.get(blockIndex);
				downTo = blockSize;
			}
		}
	}

	/** 覆盖写,在指定的位置写入一个int <br/> Writes an int at the absolute position without changing the current pointer. */
	public void writeInt(long pos, int value) {
		int blockIndex = (int) (pos >> blockBits);// 写入的block的下标
		int upto = (int) (pos & blockMask);// 写入的block的的offset
		byte[] block = blocks.get(blockIndex);
		int shift = 24;
		for (int i = 0; i < 4; i++) {
			block[upto++] = (byte) (value >> shift);
			shift -= 8;
			if (upto == blockSize) {
				upto = 0;
				blockIndex++;
				block = blocks.get(blockIndex);
			}
		}
	}

	/** 将指定的区间倒叙 <br/> Reverse from srcPos, inclusive, to destPos, inclusive. */
	public void reverse(long srcPos, long destPos) {
		assert srcPos < destPos;
		assert destPos < getPosition();

		int srcBlockIndex = (int) (srcPos >> blockBits);
		int src = (int) (srcPos & blockMask);
		byte[] srcBlock = blocks.get(srcBlockIndex);

		
		int destBlockIndex = (int) (destPos >> blockBits);
		int dest = (int) (destPos & blockMask);
		byte[] destBlock = blocks.get(destBlockIndex);

		
		int limit = (int) (destPos - srcPos + 1) / 2;
		for (int i = 0; i < limit; i++) {
			byte b = srcBlock[src];
			srcBlock[src] = destBlock[dest];
			destBlock[dest] = b;
			src++;
			if (src == blockSize) {
				srcBlockIndex++;
				srcBlock = blocks.get(srcBlockIndex);
				src = 0;
			}

			dest--;
			if (dest == -1) {
				destBlockIndex--;
				destBlock = blocks.get(destBlockIndex);
				dest = blockSize - 1;
			}
		}
	}

	/** 跳过len个位置,挪动指针,目的是创造足够的空间大小 */
	public void skipBytes(int len) {
		while (len > 0) {
			int chunk = blockSize - nextWrite;// 
			if (len <= chunk) {
				nextWrite += len;
				break;
			} else {
				len -= chunk;
				current = new byte[blockSize];
				blocks.add(current);
				nextWrite = 0;
			}
		}
	}

	/** 返回现在的指针 */
	public long getPosition() {
		return ((long) blocks.size() - 1) * blockSize + nextWrite;
	}

	/**
	 * 将指定位置以后的清除 <br/> Pos must be less than the max position written so far! Ie, you cannot "grow" the file with this!
	 */
	public void truncate(long newLen) {
		assert newLen <= getPosition();
		assert newLen >= 0;
		int blockIndex = (int) (newLen >> blockBits);//找到最后一个block和在其中的下标
		nextWrite = (int) (newLen & blockMask);
		if (nextWrite == 0) {
			blockIndex--;
			nextWrite = blockSize;
		}
		// 将最后一个block的后面的block都清空
		blocks.subList(blockIndex + 1, blocks.size()).clear();
		
		
		if (newLen == 0) {
			current = null;
		} else {
			current = blocks.get(blockIndex);
		}
		assert newLen == getPosition();
	}

	public void finish() {
		if (current != null) {
			byte[] lastBuffer = new byte[nextWrite];
			System.arraycopy(current, 0, lastBuffer, 0, nextWrite);
			blocks.set(blocks.size() - 1, lastBuffer);
			current = null;
		}
	}

	/** Writes all of our bytes to the target {@link DataOutput}. */
	public void writeTo(DataOutput out) throws IOException {
		for (byte[] block : blocks) {
			out.writeBytes(block, 0, block.length);
		}
	}
	/** 从前向后读的reader */
	public FST.BytesReader getForwardReader() {
		if (blocks.size() == 1) {
			return new ForwardBytesReader(blocks.get(0));
		}
		return new FST.BytesReader() {
			
			private byte[] current;
			private int nextBuffer;
			private int nextRead = blockSize;

			@Override
			public byte readByte() {
				if (nextRead == blockSize) {
					current = blocks.get(nextBuffer++);
					nextRead = 0;
				}
				return current[nextRead++];
			}

			@Override
			public void skipBytes(long count) {
				setPosition(getPosition() + count);
			}

			@Override
			public void readBytes(byte[] b, int offset, int len) {
				while (len > 0) {
					int chunkLeft = blockSize - nextRead;
					if (len <= chunkLeft) {
						System.arraycopy(current, nextRead, b, offset, len);
						nextRead += len;
						break;
					} else {
						if (chunkLeft > 0) {
							System.arraycopy(current, nextRead, b, offset, chunkLeft);
							offset += chunkLeft;
							len -= chunkLeft;
						}
						current = blocks.get(nextBuffer++);
						nextRead = 0;
					}
				}
			}

			@Override
			public long getPosition() {
				return ((long) nextBuffer - 1) * blockSize + nextRead;
			}

			@Override
			public void setPosition(long pos) {
				int bufferIndex = (int) (pos >> blockBits);
				nextBuffer = bufferIndex + 1;
				current = blocks.get(bufferIndex);
				nextRead = (int) (pos & blockMask);
				assert getPosition() == pos;
			}

			@Override
			public boolean reversed() {
				return false;
			}
		};
	}

	public FST.BytesReader getReverseReader() {
		return getReverseReader(true);
	}

	/** 从后向前读的, */
	FST.BytesReader getReverseReader(boolean allowSingle) {
		if (allowSingle && blocks.size() == 1) {
			return new ReverseBytesReader(blocks.get(0));
		}
		
		return new FST.BytesReader() {
			
			/** 当前读取的 */
			private byte[] current = blocks.size() == 0 ? null : blocks.get(0);
			/** 下一个byte[]的下标 */
			private int nextBuffer = -1;
			/** 当前读取的下标 */
			private int nextRead = 0;

			@Override
			public byte readByte() {//向前读,倒着读
				if (nextRead == -1) {
					current = blocks.get(nextBuffer--);
					nextRead = blockSize - 1;
				}
				return current[nextRead--];
			}

			@Override
			public void skipBytes(long count) {
				setPosition(getPosition() - count);
			}

			@Override
			public void readBytes(byte[] b, int offset, int len) {
				for (int i = 0; i < len; i++) {
					b[offset + i] = readByte();
				}
			}

			@Override
			public long getPosition() {
				return ((long) nextBuffer + 1) * blockSize + nextRead;
			}

			@Override
			public void setPosition(long pos) {//当读取的时候先调用这个方法,指向某个节点的开始位置(是倒序的,从后面开始读)
				// NOTE: a little weird because if you
				// setPosition(0), the next byte you read is
				// bytes[0] ... but I would expect bytes[-1] (ie,
				// EOF)...?
				int bufferIndex = (int) (pos >> blockBits);
				nextBuffer = bufferIndex - 1;
				current = blocks.get(bufferIndex);
				nextRead = (int) (pos & blockMask);
				assert getPosition() == pos : "pos=" + pos + " getPos()=" + getPosition();
			}

			@Override
			public boolean reversed() {
				return true;
			}
		};
	}
}
 看完了上面的内容,很多节点写入FST的过程就可以理解了,下面再看下一步,结束fst的写入,即调用Builder.finish方法:当所有的term都写完之后调用这个方法:
/**
 * 将frontier写入到fst,从0开始冷冻
 * Returns final FST. NOTE: this will return null if nothing is accepted by the FST.
 */
public FST<T> finish() throws IOException {
	final UnCompiledNode<T> root = frontier[0];//root,也就是第一个节点,开始节点
	// minimize nodes in the last word's suffix
	freezeTail(0);//将最后的frontier写入到fst中,虽然传入的是0,但是从第二个node(下标是1)开始编译到fst
	if (root.inputCount < minSuffixCount1 || root.inputCount < minSuffixCount2 || root.numArcs == 0) {//不进入
		if (fst.emptyOutput == null) {
			return null;
		} else if (minSuffixCount1 > 0 || minSuffixCount2 > 0) {
			// empty string got pruned
			return null;
		}
	} else {
		if (minSuffixCount2 != 0) {//不进入
			compileAllTargets(root, lastInput.length());
		}
	}
	fst.finish(compileNode(root, lastInput.length()).node);//将root写入fst。

	if (doPackFST) {//后续会介绍
		return fst.pack(this, 3, Math.max(10, (int) (getNodeCount() / 4)), acceptableOverheadRatio);
	} else {
		return fst;
	}
}
里面还是调用了freezeTail方法,虽然穿的是0,但是freezeTail方法中会提高到1,调用这个方法的目的是将现在的frontier写入到fst中。调用完之后所有的term才都写入到fst中了,然后调用fst的finish方法,调用之前对root节点进行编译,因为之前调用freezeTail的时候都是从第一个开始编译的,所有从root发出的arc都还没有编译呢,所以要编译root,也就是所有的从root发出的arc,获得一个编译后的节点的序号(或者绝对位置),然后调用fst.finish,代码在下一篇博客中介绍避免篇幅太长了。
如果读者发现文中有错误,请联系我的QQ:1308567317

 

分享到:
评论

相关推荐

    Lucene中的FST算法描述

    例如,如果一个FST图表示的term及其对应的输出值为"stop"对应4,那么可以将这个关系图解为如下步骤:从起始节点开始,选择标记为's'的弧线,到达节点23,并带上权重3;继续选择标记为't'的弧线,到达节点21,权重为0...

    fst-2.57-API文档-中英对照版.zip

    赠送源代码:fst-2.57-sources.jar; 赠送Maven依赖信息文件:fst-2.57.pom; 包含翻译后的API文档:fst-2.57-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:de.ruedigermoeller:fst:2.57; 标签:...

    关于Lucene的词典FST深入剖析-申艳超1

    《关于Lucene的词典FST深入剖析》这篇文章是由申艳超撰写的,主要探讨了Apache Lucene这个全文搜索引擎库中的一个关键数据结构——有限状态转换器(Finite State Transducer,简称FST)。FST在Lucene中被用于构建和...

    AES.zip_AES_AES源代码_aes c语言_c++ aes加密解密_c语言 源代码

    提供的源代码可以作为学习AES加密算法的宝贵资源,也可以直接应用于实际项目中,提供加密和解密的功能。为了确保代码安全,开发者应遵循良好的编程实践,如输入验证、错误处理和安全的内存管理,同时要注意避免常见...

    帧差法改进matlab源代码

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

    MSN 样式的按钮和进度条控件[MSNstylebuttonprogress.rar]-精品源代码

    1. **源代码**:在软件开发中,源代码是程序员用编程语言编写的原始指令集,它们被编译或解释为可执行程序。这里的"源代码"标签表明我们拥有的是未编译的代码,可以直接查看和修改,这对于理解控件的工作原理和定制...

    FST算法解析.pdf

    - **FSTNode**: 表示FST图中的一个节点,分为两种类型:`UnCompiledNode`(未编译节点,即未存储到FSTBytes中的节点)和`CompiledNode`(已编译节点,即已存储到FSTBytes中的节点)。 - **FSTArc**: 表示两个FSTNode...

    fst-2.57-API文档-中文版.zip

    赠送源代码:fst-2.57-sources.jar; 赠送Maven依赖信息文件:fst-2.57.pom; 包含翻译后的API文档:fst-2.57-javadoc-API文档-中文(简体)版.zip; Maven坐标:de.ruedigermoeller:fst:2.57; 标签:ruedigermoeller...

    Go-FST有限状态变换器的一个Go库实现

    首先,FST是一种有向图模型,其中每个节点代表一个状态,每条边代表一个转换,并带有输入符号、输出符号和可能的权重。FST能够执行从输入序列到输出序列的映射,同时保持相对简单的内部状态。在自然语言处理中,FST...

    FST:快速Java序列化的替代品

    3. **JDK兼容**:FST设计上与Java的标准序列化接口兼容,这意味着可以轻松地替换现有的序列化代码,而无需大规模重构。 4. **自定义序列化**:FST支持用户自定义序列化策略,对于特殊类型的数据结构,可以提供更...

    fst-1.60.zip

    jaque.zip,Java查询库使语言级代码表达式在运行时以表达式树的形式表示为对象,

    fst2.4及其依赖包

    《fst2.4及其依赖包详解》 在Java开发中,高效的序列化和反序列化是关键环节之一,尤其在大数据处理、网络通信以及持久化存储等场景中。FST(Fast Serialization)库是一个高性能的Java对象序列化库,旨在替代标准...

    FST算法-关新全1

    FST的优化技术主要包括:使用FST HashMap来加快判断某个节点是否已经在FST bytes中,使用Hash算法来快速判断节点是否已经被写入到FST bytes中等方面。 FST算法是Lucene中一个核心算法,具有快速检索term信息、快速...

    FST610变频器说明书.pdf

    FST610变频器是深圳市佛斯特科技有限公司生产的一款高性能矢量通用型变频器,它支持多种控制方式,如VF控制、无PG矢量控制、转矩控制,拥有丰富的参数功能,适用于对速度、转矩响应速度、低频力矩要求较高的场合。...

    fst-jit:及时编译有限状态传感器

    FST-准时制及时编译有限状态传感器实验性-尚无功能工学院最后一年的项目。 这个想法不是将FST构建为数据结构,而是构建为表现为“坏学生”的Java字节码。 每个输入都将使用巨大的lookupswitch与一个输出关联。制作...

    lucene 源码 fst.rar 分析

    3. **查找过程**:在查询时,FST会根据输入的关键词,从根节点开始,沿着对应的字符边进行遍历,直到找到一个接受节点,从而确定是否存在该词汇项。 二、FST在Lucene中的应用 1. **倒排索引**:在Lucene的倒排索引...

    SITRANS FST030使用手册(英文版).pdf

    3. **描述**:SITRANS FST030集成了Modbus和HART通信协议,这允许它与多种自动化系统无缝集成,提供实时流量数据和设备状态信息。 4. **安装/安装**:手册详细说明了如何正确地安装和定位流量计,包括选择合适的...

    fst 配置文件读取

    在IT领域,配置文件的管理和读取是系统初始化和运行中的关键步骤,特别是在软件开发、运维自动化...通过阅读README.TXT,你可以获得关于如何使用和解读“fst”配置文件的具体步骤和示例,从而更好地应用到实际工作中。

    FST文件生产工具

    可根据seed值及dat数据,计算fst文件,可用于软件加密狗的复制等。

    FST_for_Mobile_LE_1209.pdf

    **3. 测试周期优化** 设备测试往往占据整个开发周期的很大一部分。缩短测试周期有助于加速产品上市时间。FST通过以下方式帮助削减开发成本、提高产品质量并缩短测试周期: - **元测试执行工具**:FST作为商业移动...

Global site tag (gtag.js) - Google Analytics