什么是最有效的二进制文本编码?

Posted

技术标签:

【中文标题】什么是最有效的二进制文本编码?【英文标题】:What is the most efficient binary to text encoding? 【发布时间】:2010-11-01 13:19:44 【问题描述】:

到目前为止,我能找到的最接近的竞争者是 yEnc (2%) 和 ASCII85 (25% 开销)。 yEnc 似乎存在一些问题,主要是因为它使用 8 位字符集。这就引出了另一个想法:是否有基于 UTF-8 字符集的二进制文本编码?

【问题讨论】:

请注意,yEnc 不会将二进制转换为文本,它会将二进制转换为与新闻协议(NNTP)兼容的东西,它不一定满足任何字符集要求,更不用说它会所有可打印的文本。 【参考方案1】:

目前base91 是最好的编码,如果您仅限于 ASCII 字符并且不想使用不可打印的字符。它还具有快如闪电的编码/解码速度的优点,因为可以使用查找表,不像 base85 必须使用慢除法解码

超过base122 将有助于提高一点效率,但它不是 8 位干净的。但是,由于它基于 UTF-8 编码,因此可以用于多种用途。现在 8-bit clean 已经毫无意义了

请注意,base122 实际上是 base-128,因为 6 个无效值 (128 – 122) 是经过特殊编码的,因此 一系列 14 位总是最多可以用 2 表示bytes,与 base-128 完全相同,其中 7 位将被编码为 1 个字节,实际上可以优化为比 base-128 更有效

Base-122 编码

Base-122 编码一次采用 7 位的输入数据块。如果块映射到合法字符,则使用单字节 UTF-8 字符进行编码:0xxxxxxx。如果块将映射到非法字符,我们将使用两字节 UTF-8 字符:110xxxxx 10xxxxxx。由于只有六个非法代码点,我们可以只用三位来区分它们。将这些位表示为sss 为我们提供了以下格式:110sssxx 10xxxxxx。剩下的八位似乎可以编码更多的输入数据。不幸的是,表示小于 0x80 的代码点的两字节 UTF-8 字符是无效的。浏览器会将无效的 UTF-8 字符解析为错误字符。执行大于 0x80 的代码点的一种简单方法是使用格式 110sss1x 10xxxxxx,相当于对 0x80 进行按位或(这可能会得到改进,请参见第 4 节)。图 3 总结了完整的 base-122 编码。

http://blog.kevinalbs.com/base122

另见How viable is base128 encoding for scenarios like javascript strings?

【讨论】:

请注意:它可以生成用户无法复制的“控制字符”(如复制粘贴)。【参考方案2】:

根据Wikipedia

basE91 为压缩的 8 位二进制输入生成最短的纯 ASCII 输出。

【讨论】:

baseE91 比 base64 和 Z85 更高效。但在 html 中显示其输出时要小心。它使用像 (, &) 这样应该转义的字符(Z85 也有这个问题)。 我们能否用 UTF-8 做得更好?【参考方案3】:

如果您正在为大字母寻找有效的编码,您可能想尝试escapeless。 escapeless252 和 yEnc 都有 1.6% 的开销,但第一个是固定的并且是预先知道的,而后者实际上在 0 到 100% 之间,具体取决于字节的分布。

【讨论】:

【参考方案4】:

我最近需要将二进制编码为 ascii,这就是我想出的。我不知道这是否是最有效的(可能不是),但它既简单又快速。 基本上,我将一个字节编码为十六进制,但我没有使用基组 (0-9, A-F),而是使用 (a-p)。因为集合是连续的,所以不需要任何表查找。

//buff is a unsigned character array containing the binary data
//N is the number of bytes to be encoded 
string simple_encode(unsigned char *buff, int N)

    string sEncode = "";
    for(int i = 0; i<N; i++)
    
        sEncode += (97 + (buff[i] >> 4));
        sEncode += (97 + (buff[i] & 0x0F));
    
    return sEncode;


//sbuff is a string containing the encoded ascii data
//szDecoded is an unsigned char array that has been allocated to 1/2 
//the length of sbuff
//N is an integer pointer and returns the number of converted bytes
void simple_decode(string sbuff, unsigned char *szDecode, int *N)

    *N = sbuff.length()/2;
    for(int i=0; i < *N; i++)
    
        szDecode[i] = ((sbuff.at(2*i)-97) << 4) + (sbuff.at(2*i+1)-97);
    

【讨论】:

问题是以最少的开销呈现一些东西。您的编码基本上只是具有不同字母的十六进制,其开销为 100%。无需表查找或其他分支语句也可以进行十六进制编码。好吧,它丑得要命,但它至少符合一个标准。 有趣的是,到目前为止,我认为我已经输入了 2,734 个答案。这并不意味着我不能批评答案;这里已经有很多更好的了,所以我认为不需要添加一个 - 包括代码与否。我有点惊讶的是,您仍然在捍卫自己的答案,因为其他给出的答案更有效。请注意,这里的效率主要是所需的 UTF-8 编码字节数的效率,而不是 CPU 时间。【参考方案5】:

去年我搜索了最有效的二进制到文本编码。我自己意识到紧凑并不是唯一的标准。最重要的是您可以在哪里使用编码字符串。比如yEnc有2%的开销,但它是8位编码,所以它的使用非常有限。

我的选择是Z85。它有可接受的 25% 开销,编码字符串几乎可以在任何地方使用:XML、JSON、源代码等。有关详细信息,请参阅 Z85 specification。

最后,我用 C/C++ 编写了 Z85 library 并在生产中使用它。

【讨论】:

【参考方案6】:

简短的回答是:不,仍然没有。

我遇到了将尽可能多的信息编码为 JSON 字符串的问题,这意味着没有控制字符、反斜杠和引号的 UTF-8。

我出去研究了可以将多少位压缩到有效的 UTF-8 字节中。我不同意 UTF-8 带来太多开销的答案。这不是真的。

如果您只考虑单字节序列,它与标准 ASCII 一样强大。意思是每字节 7 位。但是如果你去掉所有的特殊字符,你会得到像 Ascii85 这样的东西。

但更高位面的控制字符较少。因此,如果您使用 6 字节块,您将能够对每个块编码 5 个字节。在输出中,您将获得任意长度(1 到 6 个字节)的 UTF-8 字符的任意组合。

这将为您提供比 Ascii85 更好的结果:5/6 而不是 4/5,效率是 83% 而不是 80%。理论上它会随着更大的块长度变得更好:大约 84% 的块长度为 19 字节。

在我看来,编码过程变得过于复杂,而它提供的利润却很少。所以 Ascii85 或它的一些修改版本(我现在正在查看 Z85)会更好。

【讨论】:

【参考方案7】:

在Wikipedia列出的旁边,有Bommanews:

B-News(或 bommanews)的开发是为了减轻 UUEncode 和 Base64 编码固有的开销:它使用一种新的编码方法将二进制数据填充到文本消息中。这种方法消耗更多的 CPU 资源,但它设法将 UUEncode 的损失从大约 40% 降低到 3.5%(这些数字之间的小数点不是显示器上的污垢),同时仍然避免在消息中使用 ANSI 控制代码身体。

相当于yEnc:source

yEnc 比 B-News 占用更少的 CPU 资源,开销也差不多,但它并没有避免使用所有控制代码,它只是忽略了那些(实验上)观察到的不受欢迎的代码对某些服务器的影响,这意味着它比 B-News 更不符合 RFC。

http://b-news.sourceforge.net/ http://www.iguana.be/~stef/ http://bnews-plus.sourceforge.net/

【讨论】:

Bommanews 的常见问题解答没有说明支持哪些字符编码。我认为大多数 8 位代码页,尽管 7F 可能存在,并且 这是一个控制代码 在例如在 IBM OEM 字符集中。即使在 Windows 代码页中,818D8F909D 也是控制字符。打印此资料时要小心,因为数据丢失。 @Maarten:B-News 使用的字符为 0x20 - 0xFF。每个字符都是 base-224 数字的单个数字,偏移 0x20。每一行“文本”都是一个巨大的数字,在解码和编码过程中从二进制转换为二进制。 Yenc 使用几乎完整的 0x00 到 0xFF 范围,二进制输入中的每个字节都简单地复制到文本输出中,仅转义 0x00、0x0A 和 0x0D(以及转义字符本身,我不记得那是什么)。 最后我重新审视了这个并投了反对票。 yEnc 和 B-news 用于处理新闻协议(如果我没记错的话,是 NNTP),因此这些编码并不专门针对 UTF-8、ASCII 或 Windows-1252 等字符集。请注意,这个错误也存在于问题中,所以我在这里有点不公平。 b-news 和 yEnc 在 Web 浏览器中的显示效果不佳。 base64 和 base91 可以轻松复制粘贴,而 b-news/yenc 则不能【参考方案8】:

听起来你已经有了答案,马克。 UTF-8 作为二进制编码没有用处,因为任何大于一个字节的 UTF-8 字符即使存储文本(每字节 2 位或更多位)也有超过 25% 的开销。 Base64 编码已经比这更好了。

【讨论】:

Base 64 编码与 ASCII 兼容,并且由于 UTF-8 将7F hex 下的任何字符都映射为 ASCII,因此 UTF-8 具有至少与base 64。也就是说,对于真正密集的编码,8 位编码,例如Windows-1252 可能是一个更好的主意。 在很多情况下,即使是 Windows-1252 或 ISO-8859-1 编码也会被转换为 UTF-8,从而使数据膨胀。高效的 UTF-8 编码必须代表每个 UTF-8 字符的多个字节。 Base32768 是一个尝试。 显然我的观点是,Maarten,使用 base64 比使用 multibyte UTF-8 编码更好。如果我在谈论 ASCII,我会 ASCII。暗示我错了,因为 base64 是 UTF-8 的子集,这只是毫无意义的争吵。【参考方案9】:

这实际上取决于二进制数据的性质,以及“文本”对输出的约束。

首先,如果您的二进制数据未压缩,请在编码前尝试压缩。然后我们可以假设 1/0 或单个字节的分布或多或少是随机的。

现在:为什么需要文本?通常,这是因为通信通道不会平等地通过所有字符。例如您可能需要纯 ASCII 文本,其可打印字符范围为 0x20-0x7E。你有 95 个角色可以玩。每个字符理论上可以编码 log2(95) ~= 6.57 bits per character。定义一个非常接近的变换很容易。

但是:如果您需要分隔符怎么办?现在你只有 94 个字符,等等。所以编码的选择真的取决于你的要求。

举一个极其愚蠢的例子:如果您的频道通过所有 256 个字符都没有问题,并且您不需要任何分隔符,那么您可以编写一个实现 100% 效率的简单转换。 :-) 如何做到这一点留给读者作为练习。

UTF-8 不适合传输任意编码的二进制数据。它能够传输值 0x01-0x7F,开销仅为 14%。我不确定 0x00 是否合法;可能不会。但是任何高于 0x80 的内容都会在 UTF-8 中扩展为多个字节。我会将 UTF-8 视为通过 0x01-0x7F 或 126 个唯一字符的受限通道。如果您不需要分隔符,则每个字符可以传输 6.98 位。

这个问题的一般解决方案:假设一个包含 N 个字符的字母表,其二进制编码为 0 到 N-1。 (如果编码与假设不同,则使用查找表在我们的中间 0..N-1 表示与您实际发送和接收的表示之间进行转换。)

假设字母表中有 95 个字符。现在:这些符号中的一些将代表 6 位,有些将代表 7 位。如果我们有 A 6 位符号和 B 7 位符号,那么:

A+B=95(符号总数) 2A+B=128(可以制作的7位前缀的总数。可以以6位符号开头2个前缀,也可以以7位符号开头。)

求解系统,得到:A=33,B=62。您现在构建一个符号表:

原始编码 000000 0000000 000001 0000001 ... 100000 0100000 1000010 0100001 1000011 0100010 ... 1111110 1011101 1111111 1011110

要编码,首先移出 6 位输入。如果这六位大于或等于 100001,则移动另一位。然后查找相应的 7 位输出代码,翻译成适合输出空间并发送。每次迭代您将移动 6 或 7 位输入。

要解码,接受一个字节并转换为原始输出代码。如果原始代码小于 0100001,则将相应的 6 位移到您的输出上。否则将相应的 7 位移到您的输出上。每次迭代您将生成 6-7 位的输出。

对于均匀分布的数据,我认为这是最优的。如果您知道源代码中的零比零多,那么您可能希望将 7 位代码映射到空间的开头,以便您更有可能使用 7 位代码。

【讨论】:

以上是关于什么是最有效的二进制文本编码?的主要内容,如果未能解决你的问题,请参考以下文章

对可能是二进制但通常是文本的数据进行有效的 JSON 编码

02 字符编码

二进制文件与文本文件的差异

二进制文件

#yyds干货盘点#计算机编码规则之:Base64编码

二进制文件和ASCII文件有何区别