在XML中编码二进制数据:是否有比base64更好的替代方案?

时间:2013-06-25 15:52:01

标签: xml unicode utf-8 compression xml-serialization

我想在XML文件中编码和解码二进制数据(使用Python,但无论如何)。我必须面对XML标记内容具有非法字符的事实。唯一允许的内容在XML specs中描述:

Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

这意味着不允许的是:

  • 29个Unicode控制字符是非法的(0x00 - 0x20)即( 000xxxxx ),除了0x09,0x0A,0x0D
  • 任何超过2个字节(UTF-16 +)的Unicode字符表示都是非法的(U + D800 - U + DFFF)即( 11011xxx
  • 特殊的Unicode非字符是非法的(0xFFFE - 0xFFFF)即( 11111111 1111111x
  • <,>,&根据{{​​3}}的实体内容

1个字节可以编码256种可能。有了这些限制,第一个字节限制为256-29-8-1-3 = 215个可能性

在第一个字节的215个可能性中,this post仅使用64个可能性。 Base64产生33%的开销(一旦用base64编码,6位变为1字节)。

所以我的问题很简单:是否有比base64更有效的算法来编码XML中的二进制数据?如果没有,我们应该从哪里开始创建它?(库等)

注意:你不会回答这个帖子“你不应该使用XML来编码二进制数据,因为......”。只是不要。你最多可以争论为什么不使用215种可能性来支持糟糕的XML解析器。

NB2:我不是在谈论第二个字节,但是当我们使用补充的Unicode平面时,肯定会有一些关于可用性的事项以及它应该从10xxxxxx开始遵守UTF8标准的事实。如果没有?)。

3 个答案:

答案 0 :(得分:7)

感谢Aya的Asci85链接,有很好的想法。

我在下面为我们的案例开发了它们。


UTF-8字符可能性:


对于1字节字符(0xxxxxxx):每字节96个可能性

  • + UTF-8 ASCII字符0xxxxxxx = + 2 ^ 7
  • - UTF-8控制字符000xxxxx = -2 ^ 5
  • + XML允许UTF-8控制字符(00000009,0000000A,0000000D)= + 3
  • - XML实体不允许的字符(<,>,&)= -3

编辑:这是针对XML1.0规范的。 XML 1.1 specs允许使用除0x00 ...

之外的控制字符

对于2字节字符(110xxxxx 10xxxxxx):每2字节1920个可能性

  • + UTF-8 2字节字符110xxxxx 10xxxxxx = + 2 ^ 11
  • - UTF-8非法非规范字符(1100000x 10xxxxxx)= -2 ^ 7

对于3字节字符(1110xxxx 10xxxxxx 10xxxxxx):每3字节61440个可能性

  • + UTF-8 3字节字符1110xxxx 10xxxxxx 10xxxxxx = + 2 ^ 16
  • - UTF-8非法非规范字符(11100000 100xxxxx 10xxxxxx)= -2 ^ 11
  • - Unicode保留的UTF-16代码点(11101101 101xxxxx 10xxxxxx)= -2 ^ 11

我不会对4字节字符进行计算,这是毫无意义的:可能的数量可以忽略不计,并且在此范围内有太多非法的UTF-8字符。


编码可能性在一个3字节的空间


因此,让我们看看我们可以在3字节(24位)空间上进行哪些组合:

  • 0xxxxxxx 0xxxxxxx 0xxxxxxx:那是96 * 96 * 96 = 884736的可能性
  • 0xxxxxxx 110xxxxx 10xxxxxx:那是96 * 1920 = 184320的可能性
  • 110xxxxx 10xxxxxx 0xxxxxxx:那个1920 * 96 = 184320种可能性
  • 1110xxxx 10xxxxxx 10xxxxxx:那是61440 = 61440种可能性

还有其他可能性(比如一个3字节的字符结尾或者在空格中开始,但是像4字节字符一样,很难评估(对我来说)并且可能忽略不计)。

可能性总数:

  • 3字节空间有2 ^ 24 = 16777216 可能性。
  • 该空间内的UTF-8兼容可能性为884736 + 2 * 184320 + 61440 = 1314816种可能性。

这意味着多少开销?

  • 24位空间可用位:Log2(16777216)= 24(当然!数学理解的那些)
  • 此空间的UTF-8有用位:Log2(1314816)= 20,32个有用位。
  • 这意味着我们需要24位空间来编码20,32位有用信息,即。理论上的最小开销是 18% overhead方式优于Base64的33%开销和Ascii85的25%开销!

编辑:这是针对XML1.0规范的。使用XML1.1(不广泛支持...),理论开销为12.55%。我设法制作了一个二进制安全算法,XML1.1的开销为14.7%。


如何接近18%的开销?


坏消息是,如果不使用大字典"我们就不能轻易获得18%的成功率。 (即长期套装)。但它容易获得20%,而且非常容易但却不太可行,只能获得19%。

良好的编码长度候选人:

  • 6位可以编码5位,开销为20%(2 ^(6 * 0,84)> 2 ^ 5)
  • 12位可以编码10位,开销为20%(2 ^(12 * 0,84)> 2 ^ 10)
  • 24位可编码20位,开销为20%(2 ^(24 * 0,84)> 2 ^ 20)
  • 25位可编码21位,开销为19%(2 ^(25 * 0,84)> 2 ^ 21)

注意:0,84是平均值"有用性"空位(20,32 / 24)。


如何构建我们的编码算法?


我们需要建立一个" dictionnary"这将绘制"空间可能性" (长度为5,10,20或21位的比特序列,取决于所选算法的编码长度 - 只需选择一个)到utf8兼容序列(长度为6,12,24或25的utf8位序列)比特)。

最简单的起点是将20位序列编码为24位兼容的UTF-8序列:这正是上面计算可能性的例子,而且是3个UTF-8字节的长度(所以我们不必担心未终止的UTF8字符。)

请注意,我们必须使用2字节(或以上)UTF-8字符编码空间来达到20%的开销。只有1字节长的UTF8字符,我们只能通过RADIX-24达到25%的开销。但是,3字节长的UTF-8字符无需达到20%的开销。

这是这个问题的下一个挑战。谁想玩? :)


算法提议,我将名称BaseUTF-8命名为XML


要编码的20个二进制位:ABCDEFGHIJKLMNOPQRST

产生的名为"编码":24位长的UTF-8字符串

数学编码算法(不基于任何已知的编程语言):

If GH != 00 && NO != 00:
    encoded = 01ABCDEF 0GHIJKLM 0NOPQRST # 20 bits to encode, 21 space bits with restrictions (1-byte UTF-8 char not starting by 000xxxxx ie ASCII control chars)

If ABCD != 0000:
    If GH == 00 && NO == 00: # 16 bits to encode
        encoded = 0010ABCD 01EFIJKL 01MPQRST    
    Else If GH == 00:  # 18 bits to encode, 18 space bits with restrictions (1-byte  UTF-8 ASCII control char, 2-bytes UTF-8 char noncanonical)
        encoded = 0NOFIJKL 110ABCDE 10MPQRST
    Else If NO == 00:  # 18 bits to encode
        encoded = 110ABCDE 10MPQRST 0GHFIJKL

If ABCD == 0000: # 16 bits to encode
    encoded = 0011EFGH 01IJKLMN 01OPQRST

On "encoded" variable apply:
    convert < (0x3C) to Line Feed (0x0A)
    convert > (0x3E) to Cariage Return (0x0D)
    convert & (0x26) to TAB (0x09)

这就是你如何获得20%的开销。

当要编码的字符串不是20的倍数时,该算法还没有提供管理字符串终止的方法。还必须提供解码算法,但这很容易(只是不要忘记抛出异常以强制解码的单一性。)

答案 1 :(得分:2)

我用C代码开发了这个概念。

该项目位于GitHub上,最终称为BaseXML:https://github.com/kriswebdev/BaseXML

它有20%的开销,这对二进制安全版本很有用。

我很难与Expat一起工作,后者是Python的场景XML解析器(THAT DOESN&#39; T支持XML1.1!)。因此,您将找到适用于XML1.0的BaseXML1.0二进制安全版本。

我可能会发布&#34; for XML1.1&#34;如果请求版本稍后(它也是二进制安全的并且具有14.7%的开销),它已经准备好并且在Python内置的XML解析器中工作确实无用,所以我不想让太多的人混淆版本(还)。

答案 2 :(得分:1)

更糟糕的是:您实际上并没有可以使用的215个不同的字节值。生成的二进制数据必须在XML表示的任何编码中有效(几乎可以肯定是UTF-8),这意味着禁止许多字节序列。 0xc2后跟0x41只是一个随机的例子。 XML是文本(Unicode字符序列),而不是二进制数据。传输时,它使用一些编码(几乎是alwats UTF-8)进行编码。如果你试图将它视为二进制数据,那么在我看来,你要求的方式比它值得处理的更麻烦。

如果你还想这样做......

XML是文本。所以我们不要试图将二进制数据编码为二进制数据。这不会导致一种简单或明显的方式将其展示到XML文档中。让我们尝试将二进制数据编码为文本!

让我们尝试一种非常简单的编码:

  • 将二进制数据分组为20位
  • 将每组20位编码为Unicode字符U + 10000加上20位的数值。

这意味着你只使用1到16号平面的字符。所有限制字符都在0号平面(BMP)中,所以你在这里很安全。

然后,当您将此XML文档编码为UTF-8进行传输时,每个字符都需要4个字节进行编码。因此,每20位原始数据消耗32位,与原始数据的纯二进制编码相比,这是60%的开销。 这比base64的33%差,这让它成为一个糟糕的主意。

这种编码方案有点浪费,因为它不使用BMP字符。我们可以使用BMP字符使其更好吗?不是琐碎的。 20是我们可以用于组的最大尺寸(log(0x10FFFF) ~ 20.09)。我们可以重新映射方案,尽可能使用一些manu BMP字符,因为这些字符占用较少的空间来编码UTF-8,但这不仅会使编码复杂化很多(禁用的字符也是分散的,所以我们有几种情况要处理但它只能导致约6.25%的位模式(BMP中的Unicode字符的一小部分)得到改善,而对于6.25%的大部分,我们只保存一个字节。对于随机数据,开销从60%降低到55%左右。 结果仍然比base64差得多,除了一些非常人为的数据。请注意,开销与数据有关。对于0.2%的位模式,您实际上将获得压缩而不是开销(0.012%的模式为60%压缩,0.18%的模式为20%压缩)。但这些分数真的很低。这不值得。

换句话说:如果你想用4字节UTF-8序列编码任何东西,你需要每个序列使用32位(当然),但这些位中有11位是固定的且不可更改的:这些位必须适合模式11110xxx 10xxxxxx 10xxxxxx 10xxxxxx并且那里只有21个x。 60%的开销内置于UTF-8中,因此如果您想将此作为任何编码的基础来改善base64的开销,那么您就是从后面开始的!

我希望这可以说服你使用任何此类方案都无法提高base64的密度。