C 中二进制文件的运行长度编码

问题描述

我编写了这个函数,它对 C 中的文本文件执行运行长度编码的稍微修改变体。 我试图将它推广到二进制文件,但我没有使用它们的经验。我明白,虽然我可以用与比较文本文件中的 char 相同的方式比较二进制数据的字节,但我不知道如何将一个字节的出现次数打印到压缩版本就像我在下面的代码中所做的那样。

关于我使用的 RLE 类型的说明:连续出现不止一次的字节被复制以表示下一个数字实际上是出现的次数,而不是在字符后面的数字文件。对于超过一位的出现次数,它们被分解为 9 次出现的运行。

例如,aaaaaaaaaaabccccc 变为 aa9aa2bcc5

这是我的代码

char* encode(char* str)
{
    char* ret = calloc(2 * strlen(str) + 1,1);
    size_t retIdx = 0,inIdx = 0;
    while (str[inIdx]) {
        size_t count = 1;
        size_t contIdx = inIdx;
        while (str[inIdx] == str[++contIdx]) {
            count++;
        }
        size_t tmpCount = count;

        // break down counts with 2 or more digits into counts ≤ 9
        while (tmpCount > 9) {
            tmpCount -= 9;
            ret[retIdx++] = str[inIdx];
            ret[retIdx++] = str[inIdx];
            ret[retIdx++] = '9';
        }

        char tmp[2];

        ret[retIdx++] = str[inIdx];
        if (tmpCount > 1) {
            // repeat character (this tells the decompressor that the next digit
            // is in fact the # of consecutive occurrences of this char)
            ret[retIdx++] = str[inIdx];
            // convert single-digit count to string
            snprintf(tmp,2,"%ld",tmpCount);
            ret[retIdx++] = tmp[0];
        }

        inIdx += count;
    }

    return ret;
}

为了适应二进制流需要进行哪些更改?我看到的第一个问题是 snprintf 调用,因为它使用文本格式运行。敲响警钟的事情也是我处理多位数出现运行的方式。我们不再使用 base 10,所以必须改变,我只是不确定几乎从未使用过二进制数据。

解决方法

一些对您有用的想法:

  • 将 RLE 推广到二进制数据的一种简单方法是使用基于位的压缩。例如位序列00000000011111100111可以转换为序列0 9623。由于二进制字母表仅由两个符号组成,因此您只需存储第一位值(这可以像将其存储在第一个位中一样简单),然后是连续相等值的数量。任意大的整数可以使用 Elias gamma coding 以二进制格式存储。可以添加额外的填充以将整个序列很好地放入整数字节中。所以使用这个方法,上面的序列可以这样编码:
00000000011111100111 -> 0 0001001 00110 010 011
                        ^    ^      ^    ^   ^
                first bit    9      6    2   3
  • 如果你想保持它基于字节,一个想法是考虑所有偶数字节频率(解释为无符号字符)和所有奇数字节的值。如果一个字节出现超过 255 次,那么您可以重复它。不过,这可能非常低效,但实施起来绝对简单,如果您可以对输入做出一些假设,那就足够了。

  • 此外,您可以考虑从 RLE 移出并实施 Huffman's coding 或其他复杂算法(例如 LZW)。

在实施方面,我认为 tucuxi 已经给了你一些提示。

,

你只需要解决两个问题:

  • 您不能使用任何与 str 相关的函数,因为 C 字符串不能很好地处理 '\0'。例如,strlen 将返回字符串中第一个 0x0 字节的索引。输入的长度必须作为附加参数传入:char *encode(char *start,size_t length)

  • 您的输出不能具有 strlen(ret) 的隐式长度,因为输出中可能会散布额外的 0 字节。您再次需要一个额外的参数:size_t encode(char *start,size_t length,char *output)(此版本需要在外部保留 output 缓冲区,大小至少为 length*2,并返回编码字符串的长度)

其余的代码,假设它以前可以工作,现在应该可以继续正常工作。如果您想超过 base-10,例如使用 base-256 进行更大的压缩,则只需更改分解循环中的常量(从 9255 ),并将 snprintf 替换如下:

    // before
    snprintf(tmp,2,"%ld",tmpCount);
    ret[retIdx++] = tmp[0];

    // after: much easier
    ret[retIdx++] = tmpCount;