32

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

4

9 回答 9

18

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

首先,如果您的二进制数据未压缩,请尝试在编码之前进行压缩。然后我们可以假设 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 位前缀的总数。您可以以 2 个以 6 位符号开头的前缀,或以 1 个以 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 位代码。

于 2009-06-09T17:42:55.853 回答
9

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

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

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

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

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

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

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

于 2013-08-05T10:18:04.110 回答
9

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

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

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

于 2014-04-12T21:19:46.313 回答
7

根据维基百科

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

于 2010-12-14T16:37:36.037 回答
3

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

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

请注意,base122 实际上是 base-128,因为 6 个无效值 (128 – 122) 是经过特殊编码的,因此一系列 14 位始终可以用最多 2 个字节表示,就像 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 编码。

Base-122 编码方案

http://blog.kevinalbs.com/base122

另请参阅对于 JavaScript 字符串等场景,base128 编码的可行性如何?

于 2018-04-16T16:13:12.383 回答
1

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

于 2009-06-11T17:14:55.123 回答
1

在Wikipedia上列出的那些旁边,有 Bommanews:

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

它与 yEnc 相当:来源

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

于 2012-03-29T23:07:19.690 回答
0

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

于 2019-06-03T17:51:00.760 回答
-3

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

//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);
    }
}
于 2016-12-31T00:16:50.953 回答