只需要读取字节的一种潜在方法是直接在解码例程中进行缓冲。这与基于表的解码很好地结合在一起,并且没有进行逐位 IO 的开销(用抽象层隐藏它并不会让它消失,只是将它抹在地毯下)。
在最简单的情况下,基于表的解码需要一个比特流的“窗口”,它与1尽可能大的代码一样大(顺便说一下,这种事情是很大一部分原因为什么许多使用霍夫曼压缩的格式指定的最大代码长度不是超长2),可以通过将缓冲区向右移动直到它具有正确的大小来创建:
window = buffer >> (maxCodeLen - bitsInBuffer)
因为无论如何这都会消除多余的位,所以当缓冲区不够时,可以安全地将 更多 位附加到缓冲区中:
while bitsInBuffer < maxCodeLen:
buffer = (buffer << 8) | readByte()
bitsInBuffer += 8
因此字节 IO 就足够了。实际上,如果您愿意,您可以读取稍大的块(例如,一次两个字节)。顺便说一句,这里有一点复杂:如果文件的所有字节都已被读取并且缓冲区中没有足够的位(这是有效位流可能发生的合法条件),您只需填写“填充"(基本上左移而不用 ORing 新位)。
解码本身可能如下所示:
# this line does the actual decoding
(symbol, length) = table[window]
# remove that code from the buffer
bitsInBuffer -= length
buffer = buffer & ((1 << bitsInBuffer) - 1)
# use decoded symbol
这一切都很容易,困难的部分是构建table。一种方法(不是一个好方法,而是一个简单的方法)是从 0 到并包括 (1 << maxCodeLen) - 1 的每个整数,并以你的方式使用逐位树遍历解码其中的第一个符号重新习惯了。一种更快的方法是获取每个符号/代码对并使用它来填充表格的正确条目:
# for each symbol/code do this:
bottomSize = maxCodeLen - codeLen
topBits = code << bottomSize
for bottom in range(0, (1 << bottomSize) - 1):
table[topBits | bottom] = (symbol, codeLen)
顺便说一下,这些代码都没有经过测试,只是为了大致展示它是如何完成的。它还假设了一种将比特流打包成字节的特殊方式,第一位在字节的顶部。
1:一些多阶段解码策略可以使用更小的窗口,如果没有码长限制,可能需要。
2:例如 Deflate 最大 15 位