文本压缩:编码前缀(标题)以提高性能的策略

时间:2014-05-21 13:10:47

标签: algorithm text binary compression huffman-code

我目前正在进行一个霍夫曼文本压缩练习,但是我遇到了一些编码问题的问题。我使用字符频率表来保存我需要的信息作为标题用于解压缩文件(全部转换为二进制字符串,然后保存在字节数组中)。

首先,我为每个字符使用了2个字节,字符使用了1个字节,频率使用了1个字节。但是我意识到它不适用于某些字符频率超过255(1字节)的大文本。

因此我做了修改,根据频率调整每个字符的保留字节数。它看起来像这样:

public String freString(int freq, String s){
    freq = freq - 255;
    s = s + ("11111111");

    if(freq>=255){
        s = freString(freq,s);
    }else{

        String remainFreq=  Integer.toBinaryString(freq);
       //patch the ommited zeros of last byte
       remainFreq= String.format("%8s", remainFreq);
       remainFreq=  tempString.replace(' ', '0');
       s = s + remainFreq;
    }

    return s;


}

有了这个,在解压缩期间,我将查看下一个字节,看它的值是什么,如果是255,那么继续添加下一个字节的值......等等。

标题示例:

[9, 141 ,3, 142 ,255,33, 143 ,255,255,2]

[标题长度= 9, a = 3, b = 288, c = 512]

它工作正常,但随着文本越来越大,它大大降低了我的压缩率。 例如:如果'a'重复5000次。我将使用最多 20个字节来存储我的频率值字符串,而不是 2个字节(00010011 10001000 = 5000)

所以这是我的问题......有没有更好的策略可以用来动态增加字符的保留字节,同时指示“freq字符串结束”?我虽然每个字符保留最少3个字节(1表示char,1表示freq,1表示freq字符串结束),但这会影响较小文本文件的压缩率。 这是我必须采取的权衡吗?或者有更好的方法吗?

1 个答案:

答案 0 :(得分:0)

如果你有一个霍夫曼树,那么你可以制作许多其他霍夫曼树,通过交换任何节点的左右子节点,为所有符号分配相同的长度但不同的代码字。所有这些树都同样好 - 它们压缩数据同样多,因为长度保持不变。 Canonical Huffman是一种事先同意如何从所有可能的置换树中选择一棵特定树的方法,这样你就不必知道你实际使用的那棵树中的哪一棵。

在实践中,这意味着可以从长度上重建树。 实际重建树没有必要,但重建它的能力意味着您保留了所有信息。

通常情况下,对于哪个树是规范树,您可以做出不同的选择。您所做的选择对解码技术有一些影响,但这可能超出了这个问题的范围。无论如何,一种选择是置换树,以便

  1. 最长的代码全为零
  2. 较短的代码(在右侧用零填充到与最长代码相同的长度)在数字上大于所有更长的代码
  3. 具有相同长度的符号按升序分配代码
  4. 规则1和2制作一侧最深的树,另一侧最浅的树,其间没有奇怪的跳跃。规则3命令处于相同深度的节点。

    事实证明,你不需要进行任何树重组。只使用分配给每个符号的长度,可以轻松构建代码,如下所示:

    // create histogram of lengths
    const int maxcodelength = 15; // 15 is the max code length for Deflate
    uint[] length_count = new uint[maxcodelength + 1];;
    for (int i = 0; i < symbollengths.Length; i++)
        length_count[symbollengths[i]]++;
    
    // find starting point (lowest code) for each length
    uint code = 0;
    uint[] next_code = new uint[maxcodelength + 1];
    next_code[maxcodelength] = 0;
    for (int bits = maxcodelength - 1; bits >= 0; bits--)
    {
        code = (code + length_count[bits + 1]) >> 1;
        next_code[bits] = code;
    }
    
    // assign codes to symbols
    uint[] codes = new uint[256];
    for (int n = 0; n < codes.Length; n++)
    {
        int len = symbollengths[n];
        if (len != 0)
        {
            codes[n] = next_code[len];
            next_code[len]++;
        }
    }
    

    这与rfc1951 (Deflate)第8页上的代码密切相关,但不同(转移是另一种方式,导致全零代码具有最长的长度,在Deflate中,全零代码具有最短的长度)。

    至于标题,现在每个符号只需要4位(如果你也使用15的长度限制),每个符号肯定不超过8位(代码长于256会有点疯狂)。对于标题,这仍然是128或256字节(对于256的字母表)。你可以改进这一点,例如借用Deflate的游程长度编码方案。


    其他内容。

    保证不超过最大长度的一种方法是将所有频率除以2(向上舍入)并重新创建霍夫曼树,直到不再超过最大长度。还有其他方法可以计算有效的长度集,而无需构建树,例如package-merge

    几乎所有使用霍夫曼编码的压缩格式都限制了长度。它对编码和解码都很重要,主要用于解码。对于编码,代码不超过25意味着您可以使用32位缓冲区并写出字节(意味着缓冲区中最多可以保留7位),而无需特殊情况,因为在缓冲区中添加代码会溢出。对于解码,短(ish)最大代码长度允许简单的单表查找 - 它当时用maxcodelength位(&#34;窗口&#34;)索引,给出第一个符号(窗口中的实际解码和该符号的长度(因此它可以移出)。如果最大代码长度较长,则需要稍微复杂的技术,例如多级表,或我个人喜欢的,不同的表,具体取决于窗口中前导零的数量。