我正在尝试在C#中实现霍夫曼编码。我对大型文件进行编码存在问题,因为这需要花费太多时间。例如,要编码11MiB二进制文件,在调试模式下需要10秒。而且,我什至不必费心等待程序完成27MiB文件。
这是有问题的循环:
BitArray bits = new BitArray(8);
byte[] byteToWrite = new byte[1];
byte bitsSet = 0;
while ((bytesRead = inputStream.Read(buffer, 0, 4096)) > 0) // Read input in chunks
{
for (int i = 0; i < bytesRead; i++)
{
for (int j = 0; j < nodesBitStream[buffer[i]].Count; j++)
{
if (bitsSet != 8)
{
bits[bitsSet] = nodesBitStream[buffer[i]][j];
bitsSet++;
}
else
{
bits.CopyTo(byteToWrite, 0);
outputStream.Write(byteToWrite, 0, byteToWrite.Length);
bits = new BitArray(8);
bitsSet = 0;
bits[bitsSet] = nodesBitStream[buffer[i]][j];
bitsSet++;
}
}
}
}
nodesBitStream
是Dictionary<byte, List<bool>>
。 List<bool>
表示从霍夫曼树根到叶节点的路径,该路径包含以byte
表示的特定符号。
因此,我正在累积位以形成一个字节,然后将其写入已编码的文件。很明显,这可能需要很长时间,但是我还没有找到其他方法。因此,我正在寻求有关如何加快流程的建议。
答案 0 :(得分:2)
一点一点地工作是很多额外的工作。同样,尽管Dictionary<byte, TVal>
不错,但普通数组甚至更快。
霍夫曼码也可以表示为一对整数,一个整数表示长度(以位为单位),另一个表示位数。在这种表示形式中,您可以通过几个快速操作来处理符号,例如(未测试):
BinaryWriter w = new BinaryWriter(outStream);
uint buffer = 0;
int bufbits = 0;
for (int i = 0; i < symbols.Length; i++)
{
int s = symbols[i];
buffer <<= lengths[s]; // make room for the bits
bufbits += lengths[s]; // buffer got longer
buffer |= values[s]; // put in the bits corresponding to the symbol
while (bufbits >= 8) // as long as there is at least a byte in the buffer
{
bufbits -= 8; // forget it's there
w.Write((byte)(buffer >> bufbits)); // and save it
}
}
if (bufbits != 0)
w.Write((byte)(buffer << (8 - bufbits)));
或某些变体,例如,您可以以其他方式填充字节,或将字节保存在数组中并进行更大的写操作,等等。
此代码要求将代码长度限制为最大25位,通常其他要求会进一步降低该限制。不需要很大的代码长度即可获得良好的压缩率。
答案 1 :(得分:2)
我真的不知道算法的工作原理,但是看一下您的代码,有两点值得关注:
您似乎正在使用字典来索引一个字节。使用List<bool>[]
索引到一个简单的buffer[i]
可能会更快。您要支付的内存价格相当低。使用数组可以交换具有更快偏移量的查找。您正在那里进行很多查找。
为什么要在每次迭代中实例化bits
?取决于您执行的迭代次数,最终可能会给GC
带来压力。似乎没有必要,您实际上是在覆盖每个位,然后每8位将其吐出,因此只需覆盖它,而不是对其进行更新;一遍又一遍地使用同一实例。