JSON字符串中的二进制数据。比Base64更好的东西

时间:2009-09-18 08:08:06

标签: json base64

JSON format本身不支持二进制数据。必须对二进制数据进行转义,以便可以将它放入JSON中的字符串元素(即使用反斜杠转义的双引号中的零个或多个Unicode字符)。

转义二进制数据的一个明显方法是使用Base64。但是,Base64具有很高的处理开销。它还将3个字节扩展为4个字符,这使得数据量增加了大约33%。

一个用例就是CDMI cloud storage API specification的v0.8草案。您可以使用JSON通过REST-Webservice创建数据对象,例如

PUT /MyContainer/BinaryObject HTTP/1.1
Host: cloud.example.com
Accept: application/vnd.org.snia.cdmi.dataobject+json
Content-Type: application/vnd.org.snia.cdmi.dataobject+json
X-CDMI-Specification-Version: 1.0
{
    "mimetype" : "application/octet-stream",
    "metadata" : [ ],
    "value" :   "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
    IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
    dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
    dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
    ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",
}

是否有更好的方法和标准方法将二进制数据编码为JSON字符串?

19 个答案:

答案 0 :(得分:414)

根据JSON规范,有94个Unicode字符可以表示为一个字节(如果您的JSON以UTF-8格式传输)。考虑到这一点,我认为你可以在空间上做的最好的是base85,它代表四个字节为五个字符。然而,这比base64仅提高了7%,计算起来更加昂贵,并且实现不如base64那么常见,因此它可能不是一个胜利。

您也可以简单地将每个输入字节映射到U + 0000-U + 00FF中的相应字符,然后执行JSON标准所要求的最小编码来传递这些字符;这里的优点是所需的解码不超过内置函数,但空间效率很差 - 105%的扩展(如果所有输入字节都相同),而base85为25%,base64为33%。

最终裁决:在我看来,base64胜出,理由是它是常见的,简单的,而且不错足够以保证更换。

另请参阅:Base91

答案 1 :(得分:214)

我遇到了同样的问题,并且认为我会分享一个解决方案: multipart / form-data。

通过发送多部分表单,您首先将 JSON元数据作为字符串发送,然后单独发送为内容索引的原始二进制文件(图像,wavs等) -Disposition name。

这里有一个很好的tutorial关于如何在obj-c中执行此操作,这里是a blog article,它解释了如何使用表单边界对字符串数据进行分区,并将其与二进制数据。

您真正需要做的唯一改变是在服务器端;您必须捕获应该适当引用POST二进制数据的元数据(通过使用Content-Disposition边界)。

当然,它需要在服务器端进行额外的工作,但如果您要发送许多图像或大图像,这是值得的。如果需要,可以将其与gzip压缩相结合。

IMHO发送base64编码数据是一个黑客攻击; RFC multipart / form-data是为这样的问题创建的:结合文本或元数据发送二进制数据。

答案 2 :(得分:31)

BSON(二进制JSON)可能适合您。 http://en.wikipedia.org/wiki/BSON

编辑: 仅供参考,如果你正在寻找一些C#服务器端爱,那么.NET库json.net支持读写bson。

答案 3 :(得分:27)

UTF-8的问题在于它不是空间效率最高的编码。此外,一些随机二进制字节序列是无效的UTF-8编码。因此,您不能将随机二进制字节序列解释为某些UTF-8数据,因为它将是无效的UTF-8编码。这种对UTF-8编码的约束的好处在于它使得它很可靠并且可以定位多字节字符开始和结束我们开始查看的任何字节。

因此,如果在[0..127]范围内编码字节值只需要UTF-8编码中的一个字节,则编码[128..255]范围内的字节值将需要2个字节! 比那更糟糕。在JSON中,控制字符“和\”不允许出现在字符串中。所以二进制数据需要进行一些转换才能正确编码。

让我们看看。如果我们假设在我们的二进制数据中均匀分布的随机字节值,则平均来说,一半字节将在一个字节中编码,另一半在两个字节中编码。 UTF-8编码的二进制数据将具有初始大小的150%。

Base64编码仅增长到初始大小的133%。所以Base64编码效率更高。

使用其他Base编码怎么样?在UTF-8中,对128个ASCII值进行编码是最节省空间的。在8位中,您可以存储7位。因此,如果我们以7位块的形式剪切二进制数据以将它们存储在UTF-8编码字符串的每个字节中,则编码数据将仅增长到初始大小的114%。比Base64好。不幸的是,我们不能使用这个简单的技巧,因为JSON不允许一些ASCII字符。 ASCII([0..31]和127)的33个控制字符和“和\”必须被排除。这样我们只剩下128-35 = 93个字符。

因此理论上我们可以定义一个Base93编码,它将编码大小增加到8 / log2(93)= 8 * log10(2)/ log10(93)= 122%。但Base93编码不如Base64编码方便。 Base64需要以6位块的形式切割输入字节序列,以便简单的按位运算。除133%外,不超过122%。

这就是我独立得出的结论,即Base64确实是用JSON编码二进制数据的最佳选择。我的答案提出了理由。我同意从性能的角度来看它并不是很吸引人,但也要考虑使用JSON的好处,它的人类可读字符串表示易于在所有编程语言中操作。

如果性能至关重要,则应将纯二进制编码视为JSON的替代。但是对于JSON,我的结论是Base64是最好的。

答案 4 :(得分:18)

如果您处理带宽问题,请先尝试在客户端压缩数据,然后再使用base64-it。

此类魔法的好例子是http://jszip.stuartk.co.uk/,有关此主题的更多讨论是JavaScript implementation of Gzip

答案 5 :(得分:17)

yEnc可能适合您:

http://en.wikipedia.org/wiki/Yenc

  

“yEnc是用于传输二进制文件的二进制到文本编码方案   [text]中的文件。它减少了以前基于US-ASCII的开销   使用8位扩展ASCII编码方法编码方法。   yEnc的开销通常是(如果每个字节值大约出现   相比之下,平均频率相同,只有1-2%   6位编码方法(如uuencode和Base64)的开销为33%-40%。   ...到2003年,yEnc成为事实上的标准编码系统   Usenet上的二进制文件。“

但是,yEnc是一个8位编码,因此将其存储在JSON字符串中与存储原始二进制数据具有相同的问题 - 以天真的方式执行它意味着100%的扩展,这比base64更差。 / p>

答案 6 :(得分:9)

Smile format

编码,解码和压缩非常快

速度比较(基于java,但仍有意义):https://github.com/eishay/jvm-serializers/wiki/

它也是JSON的扩展,允许您跳过字节数组的base64编码

当空间很重要时,可以对微笑编码的字符串进行gzip压缩

答案 7 :(得分:7)

虽然base64的扩展速率约为33%,但处理开销并不一定要高于此:它实际上取决于您使用的JSON库/工具包。编码和解码是简单的直接操作,甚至可以通过字符编码进行优化(因为JSON仅支持UTF-8/16/32) - 对于JSON字符串条目,base64字符始终是单字节。 例如,在Java平台上,有些库可以相当有效地完成工作,因此开销主要是由于扩展的大小。

我同意前两个答案:

  • base64是简单的,常用的标准,所以不太可能找到更好的特别用于JSON的东西(后记录等使用base-85;但是当你想到它时,好处是最好的边缘)。
  • 编码前(和解码后)压缩可能很有意义,具体取决于您使用的数据

答案 8 :(得分:4)

编辑7年后: Google Gears已消失。请忽略此答案。)


Google Gears团队遇到了缺乏二进制数据类型的问题,并试图解决这个问题:

  

<强> Blob API

     

JavaScript有一个内置的文本字符串数据类型,但没有二进制数据。 Blob对象试图解决此限制。

也许你可以用某种方式编织它。

答案 9 :(得分:3)

由于您正在寻找能够将二进制数据转换为严格基于文本且非常有限的格式的能力,我认为与您期望使用JSON维护的便利性相比,Base64的开销很小。如果需要考虑处理能力和吞吐量,那么您可能需要重新考虑文件格式。

答案 10 :(得分:2)

只是为了讨论添加资源和复杂性的立场。由于PUT / POST和PATCH用于存储新资源并对其进行修改,因此应该记住,内容传输是存储内容的精确表示,并通过发出GET操作来接收。

多部分消息通常用作救世主,但出于简单原因和更复杂的任务,我更喜欢将内容作为一个整体给出的想法。这是自我解释,很简单。

是的,JSON是一种瘫痪的东西,但最终JSON本身就是冗长的。映射到BASE64的开销很小。

正确使用多部分消息必须要拆除要发送的对象,使用属性路径作为自动组合的参数名称,或者需要创建另一个协议/格式来表达有效负载。

同样喜欢BSON方法,这并不像人们希望的那样广泛且容易地得到支持。

基本上,我们只是错过了一些东西,但嵌入二进制数据作为base64已经很好地建立起来了,除非你确实已经确定需要进行真正的二进制传输(这种情况很少见)。

答案 11 :(得分:1)

在 Node.js 中,您可以将 Buffer 转换为字符串并返回,无需任何更改:

const serialized = buffer.toString("binary")
const deserialized = Buffer.from(serialized, "binary")

如果您希望通过牺牲尺寸来获得更高的可靠性,请将 "binary" 替换为 "base64"

答案 12 :(得分:0)

另一个更新颖的想法是通过 uuencode 对数据进行编码。它基本上已被弃用,但它可能仍然是一种替代方法。 (虽然可能不是很严重。)

答案 13 :(得分:0)

只是为了添加我们低级恐龙程序员使用的另一个选项......

一种在时间黎明后三年就一直存在的老派方法是 Intel HEX 格式。它成立于 1973 年,UNIX 时代始于 1970 年 1 月 1 日。

  • 效率更高吗?号
  • 这是一个完善的标准吗?是的。
  • 它是否像 JSON 一样可读?是的,而且比大多数二进制解决方案更具可读性。

json 看起来像:

{
    "data": [
    ":10010000214601360121470136007EFE09D2190140",
    ":100110002146017E17C20001FF5F16002148011928",
    ":10012000194E79234623965778239EDA3F01B2CAA7",
    ":100130003F0156702B5E712B722B732146013421C7",
    ":00000001FF"
    ]
}

答案 14 :(得分:0)

我进一步挖掘了一点(在base128的实现过程中),并揭示了当我们发送的ASCII码大于128的字符时,浏览器(chrome)实际上发送了两个字符(字节)而是一个:(。原因是默认情况下的JSON使用utf8字符,对于这些字符,ASCII码高于127的字符由两个字节编码,chmike答案中提到。我以此方式进行了测试:在chrome网址栏中输入 chrome:// net-export / ,选择“包含原始字节”,开始捕获,发送POST请求(使用底部的代码段),停止捕获并将json文件与原始文件一起保存请求数据。然后我们查看该json文件:

  • 我们可以通过找到字符串4142434445464748494a4b4c4d4e来找到我们的base64请求,这是ABCDEFGHIJKLMN的十六进制编码,我们将看到它的"byte_count": 639
  • 我们可以通过找到字符串C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38B(这是字符¼½ÀÁÂÃÄÅÆÇÈÉÊË的request-hex utf8代码)来找到我们的above127请求(但是,这些字符的ascii十六进制代码是c1c2c3c4c5c6c7c8c9cacbcccdce)。 "byte_count": 703比base64请求长64个字节,因为在请求中,ASCII码高于127的字符是2个字节的代码:(

因此,实际上我们发送代码> 127 :(。的字符是没有好处的。对于base64字符串,我们也没有观察到这种负面行为(可能也是针对base85的,我也没有检查过)-但是,这可能是一些解决方案问题将以Ælex answer中所述的POST multipart / form-data的二进制部分发送数据(但是通常在这种情况下,我们根本不需要使用任何基本编码...)。

另一种方法可能依赖于将两个字节的数据部分映射为一个有效的utf8字符,方法是使用 base65280 / base65k 这样的代码进行编码,但由于{{3} } ...

function postBase64() {
  let formData = new FormData();
  let req = new XMLHttpRequest();

  formData.append("base64ch", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
  req.open("POST", '/testBase64ch');
  req.send(formData);
}


function postAbove127() {
  let formData = new FormData();
  let req = new XMLHttpRequest();

  formData.append("above127", "¼½ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüý");
  req.open("POST", '/testAbove127');
  req.send(formData);
}
<button onclick=postBase64()>POST base64 chars</button>
<button onclick=postAbove127()>POST chars with codes>127</button>

答案 15 :(得分:0)

如果您使用的是Node,我认为最有效,最简单的方法是使用以下命令转换为UTF16:

Buffer.from(data).toString('utf16le');

您可以通过以下方式取回数据:

Buffer.from(s, 'utf16le');

答案 16 :(得分:0)

参考:http://snia.org/sites/default/files/Multi-part%20MIME%20Extension%20v1.0g.pdf

它描述了一种使用“CDMI内容类型”操作在CDMI客户端和服务器之间传输二进制数据的方法,而无需对二进制数据进行base64转换。

如果您可以使用“非CDMI内容类型”操作,则最好将“数据”传输到对象或从对象传输。之后可以将元数据作为后续的“CDMI内容类型”操作添加到对象/从对象中检索元数据。

答案 17 :(得分:0)

数据类型确实令人担忧。我已经测试了从RESTful资源发送有效负载的不同场景。对于编码,我使用过Base64(Apache)和压缩GZIP(java.utils.zip。*)。有效载荷包含有关电影,图像和音频文件的信息。我压缩并编码了图像和音频文件,这大大降低了性能。压缩前的编码结果很好。图像和音频内容以编码和压缩字节[]的形式发送。

答案 18 :(得分:-1)

我的解决方案现在,XHR2正在使用ArrayBuffer。 ArrayBuffer作为二进制序列包含多部分内容,视频,音频,图形,文本等,具有多种内容类型。一体化响应。

在现代浏览器中,为不同的组件提供DataView,StringView和Blob。 另请参阅:http://rolfrost.de/video.html了解更多详情。