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

时间:2009-06-09 16:43:52

标签: character-encoding binary-data

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

9 个答案:

答案 0 :(得分:13)

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

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

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

但是:如果你需要一个分隔符,怎么办?现在你只有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位。如果我们有一个6位符号和B 7位符号,那么:

A + B = 95(符号总数) 2A + B = 128(可以生成7位前缀的总数。您可以使用6位符号开始2个前缀,或者使用7位符号开始前缀。)

解决系统问题:A = 33,B = 62。您现在构建一个符号表:

Raw     Encoded
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位代码。

答案 1 :(得分:8)

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

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

我出去研究了你能用多少比特挤进有效的UTF-8字节。我不同意答案,说明UTF-8带来了太多的开销。事实并非如此。

如果只考虑一个字节的序列,它就像标准ASCII一样强大。含义每字节7位。但如果你删掉所有特殊字符,你会留下类似Ascii85的东西。

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

这会给你一个比Ascii85更好的结果:5/6而不是4 / 5,83%效率而不是80%。从理论上讲,如果块长度更长,它会变得更好:在19字节块时大约为84%。

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

答案 2 :(得分:7)

根据Wikipedia

  

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

答案 3 :(得分:7)

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

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

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

答案 4 :(得分:1)

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

答案 5 :(得分:1)

Wikipedia上列出的内容旁边有Bommanews:

  

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

它与yEnc相当:source

  

yEnc比B-News的CPU密集程度更低,并且达到了相同的低开销水平,但它并没有避免使用所有控制代码,只是遗漏了那些(实验上)被观察到的不受控制的代码。对某些服务器的影响,这意味着它比B-News更不符合RFC。

答案 6 :(得分:1)

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

高于base122将有助于提高效率,但它不是8位干净。但是因为它基于UTF-8编码,所以它可以用于许多目的。而8位清洁现在毫无意义

  

Base-122编码

     

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

     

Base-122 encoding scheme

     

http://blog.kevinalbs.com/base122

答案 7 :(得分:0)

如果您正在寻找大字母的有效编码,则可能需要尝试escapeless。 escapeless252和yEnc都有1.6%的开销,但是前者是固定的并且事先已知,而后者的实际范围是0到100%,具体取决于字节的分布。

答案 8 :(得分:-1)

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

//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);
    }
}