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 字符串?
JSON.parse
等......
根据 JSON 规范(如果您的 JSON 以 UTF-8 传输),有 94 个 Unicode 字符可以表示为一个字节。考虑到这一点,我认为您可以在空间方面做的最好的事情是 base85 ,它将四个字节表示为五个字符。但是,这仅比 base64 提高了 7%,计算成本更高,并且实现不如 base64 常见,因此它可能不是胜利。
您也可以简单地将每个输入字节映射到 U+0000-U+00FF 中的相应字符,然后执行 JSON 标准所需的最小编码来传递这些字符;这里的优点是,除了内置函数之外,所需的解码为零,但空间效率很差——扩展了 105%(如果所有输入字节的可能性相同),而 base85 为 25%,base64 为 33%。
最终判决:在我看来,base64 获胜,因为它很常见,很容易,而且还不足以保证更换。
我遇到了同样的问题,并认为我会分享一个解决方案:multipart/form-data。
通过发送多部分表单,您首先将 JSON 元数据作为字符串发送,然后作为由 Content-Disposition 名称索引的原始二进制文件(图像、wav 等)单独发送。
这是关于如何在 obj-c 中执行此操作的一个很好的 tutorial,这里是 a blog article,它解释了如何使用表单边界对字符串数据进行分区,并将其与二进制数据分开。
您真正需要做的唯一更改是在服务器端;您将必须捕获您的元数据,这些元数据应该适当地引用 POST 的二进制数据(通过使用 Content-Disposition 边界)。
当然,它需要在服务器端进行额外的工作,但如果您要发送许多图像或大图像,这是值得的。如果需要,可以将其与 gzip 压缩结合使用。
恕我直言,发送 base64 编码数据是一种黑客行为; RFC multipart/form-data 是针对以下问题创建的:发送二进制数据与文本或元数据。
BSON(二进制 JSON)可能适合你。 http://en.wikipedia.org/wiki/BSON
编辑:仅供参考,如果您正在寻找一些 C# 服务器端的爱,.NET 库 json.net 支持读写 bson。
UTF-8 的问题在于它不是最节省空间的编码。此外,一些随机二进制字节序列是无效的 UTF-8 编码。因此,您不能将随机二进制字节序列解释为某些 UTF-8 数据,因为它将是无效的 UTF-8 编码。对 UTF-8 编码的这种约束的好处是,它使它变得健壮并且可以定位多字节字符的开始和结束我们开始查看的任何字节。
因此,如果在 [0..127] 范围内编码一个字节值在 UTF-8 编码中只需要一个字节,那么在 [128..255] 范围内编码一个字节值将需要 2 个字节!比这更糟糕。在 JSON 中,控制字符、" 和 \ 不允许出现在字符串中。因此二进制数据需要进行一些转换才能正确编码。
让我们看看。如果我们假设二进制数据中的随机字节值是均匀分布的,那么平均而言,一半字节将被编码为一个字节,另一半字节被编码为两个字节。 UTF-8 编码的二进制数据将具有初始大小的 150%。
Base64 编码仅增长到初始大小的 133%。所以Base64编码效率更高。
使用另一种基本编码怎么样?在 UTF-8 中,对 128 个 ASCII 值进行编码是最节省空间的。在 8 位中,您可以存储 7 位。因此,如果我们将二进制数据切割成 7 位块以将它们存储在 UTF-8 编码字符串的每个字节中,则编码数据将仅增长到初始大小的 114%。优于 Base64。不幸的是,我们不能使用这个简单的技巧,因为 JSON 不允许一些 ASCII 字符。 ASCII 的 33 个控制字符([0..31] 和 127)以及 " 和 \ 必须排除。这只剩下 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 是最好的。
base64 imgfile | gzip | wc -l
,原始图像文件仅增长了几个 %,因此由于 base64 非常简单,gzip 传输几乎是给定的,使用 base64 确实是压缩数据的压缩 JSON 传输的好主意。但是,将未压缩的数据转换为 base64 然后 gzip 产生的字节数比 gzip+base64+gzip 高得多。
如果您处理带宽问题,请尝试先在客户端压缩数据,然后再进行 base64-it。
这种魔法的好例子在 http://jszip.stuartk.co.uk/,关于这个主题的更多讨论在 JavaScript implementation of Gzip
Content-Encoding
),因为 base64 压缩得非常好。
yEnc 可能适合您:
http://en.wikipedia.org/wiki/Yenc
“yEnc 是一种二进制到文本的编码方案,用于在 [text] 中传输二进制文件。它通过使用 8 位扩展 ASCII 编码方法减少了以前基于 US-ASCII 编码方法的开销。yEnc 的开销通常是(如果每个字节值平均出现的频率大致相同)低至 1–2%,而 uuencode 和 Base64 等 6 位编码方法的开销为 33%–40%。...到 2003 年,yEnc 成为事实上的标准Usenet 上二进制文件的编码系统。”
但是,yEnc 是 8 位编码,因此将其存储在 JSON 字符串中与存储原始二进制数据存在相同的问题——以天真的方式进行存储意味着大约 100% 的扩展,这比 base64 更糟糕。
虽然 base64 的扩展率确实约为 33%,但处理开销并不一定比这大得多:它实际上取决于您使用的 JSON 库/工具包。编码和解码是简单直接的操作,甚至可以针对字符编码进行优化(因为 JSON 仅支持 UTF-8/16/32)——对于 JSON 字符串条目,base64 字符始终是单字节的。例如,在 Java 平台上,有一些库可以相当有效地完成这项工作,因此开销主要是由于扩展了大小。
我同意之前的两个答案:
base64 是简单的常用标准,因此不太可能找到更好的专门用于 JSON 的东西(postscript 等使用 base-85;但考虑到它的好处充其量是微不足道的)
编码前(和解码后)的压缩可能很有意义,具体取决于您使用的数据
编码、解码和压缩都非常快
速度比较(基于 java 但有意义):https://github.com/eishay/jvm-serializers/wiki/
它也是 JSON 的扩展,允许您跳过字节数组的 base64 编码
当空间很重要时,可以压缩微笑编码的字符串
由于您正在寻找将二进制数据强制转换为严格基于文本且非常有限的格式的能力,因此与您期望使用 JSON 维护的便利性相比,我认为 Base64 的开销是最小的。如果处理能力和吞吐量是一个问题,那么您可能需要重新考虑您的文件格式。
(7 年后编辑:Google Gears 不见了。忽略这个答案。)
Google Gears 团队遇到了缺少二进制数据类型的问题,并试图解决它:
Blob API JavaScript 具有用于文本字符串的内置数据类型,但没有用于二进制数据。 Blob 对象试图解决这个限制。
也许你可以以某种方式编织它。
只是为了在讨论中添加资源和复杂性的观点。由于执行 PUT/POST 和 PATCH 是为了存储和更改新资源,因此应该记住内容传输是存储内容的精确表示,并且通过发出 GET 操作接收。
多部分消息通常被用作救星,但出于简单原因和更复杂的任务,我更喜欢将内容作为一个整体提供的想法。这是不言自明的,而且很简单。
是的,JSON 是很糟糕的东西,但最终 JSON 本身是冗长的。而且映射到 BASE64 的开销是一种很小的方式。
正确使用多部分消息,必须拆除要发送的对象,使用属性路径作为自动组合的参数名称,或者需要创建另一种协议/格式来表达有效负载。
也喜欢 BSON 方法,这并不像人们希望的那样得到广泛和容易的支持。
基本上,我们只是在这里遗漏了一些东西,但是将二进制数据嵌入为 base64 已经很成熟了,除非你真的确定需要进行真正的二进制传输(这种情况很少见)。
只是为了添加另一个我们低级恐龙程序员使用的选项......
Intel HEX 格式是一种在时间黎明后三年就一直存在的老派方法。它成立于 1973 年,UNIX 时代始于 1970 年 1 月 1 日。
是不是更有效率?不。
它是一个完善的标准吗?是的。
它像 JSON 一样可读吗?是的,比大多数二进制解决方案更具可读性。
json 看起来像:
{
"data": [
":10010000214601360121470136007EFE09D2190140",
":100110002146017E17C20001FF5F16002148011928",
":10012000194E79234623965778239EDA3F01B2CAA7",
":100130003F0156702B5E712B722B732146013421C7",
":00000001FF"
]
}
在 Node.js 中,您可以将 Buffer 转换为字符串并返回而无需任何更改:
const serialized = buffer.toString("binary")
const deserialized = Buffer.from(serialized, "binary")
如果您希望通过牺牲大小来获得更高的可靠性,请将 "binary"
替换为 "base64"
深入
我再挖掘一点(在 base128 的实现过程中),并揭示 当我们发送的字符大于 128 时,浏览器(chrome)实际上发送两个字符(字节)而不是一个 :(< /strong>。原因是 JSON 默认使用 utf8 字符,其中 ascii 代码高于 127 的字符由 chmike 答案提到的两个字节编码。我以这种方式进行测试:输入 chrome url bar chrome://net-export/ ,选择“Include raw bytes”,开始捕获,发送 POST 请求(使用底部的代码片段),停止捕获并保存带有原始请求数据的 json 文件。然后我们看看里面那个json文件:
我们可以通过查找字符串 4142434445464748494a4b4c4d4e 找到我们的 base64 请求,这是 ABCDEFGHIJKLMN 的十六进制编码,我们将看到“byte_count”:639。
我们可以通过查找字符串 C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38B 找到我们的上述127 请求,这是 ¼½ÀÁÂÃÄÄÅÆÇÈÉÊË 字符的请求十六进制 utf8 代码(但是此字符的 ascii 十六进制代码是 c1c2c9c4cc5c6c7c)。 "byte_count": 703 所以它比 base64 请求长 64 字节,因为 ascii 代码高于 127 的字符在请求中按 2 字节编码:(
所以实际上我们发送代码&gt;127的字符并没有好处:(。对于base64字符串,我们没有观察到这种负面行为(可能也适用于base85 - 我不检查它) - 但是可能是这个问题的一些解决方案在 Ælex answer 中描述的 POST multipart/form-data 的二进制部分发送数据(但通常在这种情况下,我们根本不需要使用任何基本编码......)。
另一种方法可能依赖于使用 base65280 / base65k 之类的代码将两个字节数据部分映射到一个有效的 utf8 字符,但由于 utf8 specification ...
函数 postBase64() { 让 formData = new FormData();让 req = new XMLHttpRequest(); formData.append("base64ch", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); req.open("POST", '/testBase64ch'); req.send(formData); } function postAbove127() { let formData = new FormData();让 req = new XMLHttpRequest(); formData.append("above127", "¼½ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáââãäåæçèééêëììíïïðñòóôõö÷øùúûüý"); req.open("POST", '/testAbove127'); req.send(formData); }
数据类型确实令人担忧。我已经测试了从 RESTful 资源发送有效负载的不同场景。对于编码,我使用了 Base64(Apache)和压缩 GZIP(java.utils.zip.*)。有效载荷包含有关电影、图像和音频文件的信息。我已经压缩和编码了图像和音频文件,这大大降低了性能。压缩前的编码效果很好。图像和音频内容作为编码和压缩字节 [] 发送。
参考:http://snia.org/sites/default/files/Multi-part%20MIME%20Extension%20v1.0g.pdf
它描述了一种使用“CDMI 内容类型”操作在 CDMI 客户端和服务器之间传输二进制数据的方法,而无需对二进制数据进行 base64 转换。
如果您可以使用“非 CDMI 内容类型”操作,那么将“数据”传输到/从对象传输是理想的。元数据随后可以作为后续的“CDMI 内容类型”操作添加到对象/从对象中检索。
我现在的解决方案,XHR2 正在使用 ArrayBuffer。 ArrayBuffer 作为二进制序列包含具有多种内容类型的多部分内容、视频、音频、图形、文本等。一站式响应。
在现代浏览器中,具有不同组件的 DataView、StringView 和 Blob。另请参阅:http://rolfrost.de/video.html 了解更多详情。
[16, 2, 38, 89]
,效率非常低。
base64.b85encode()
和b85decode()
。一个简单的编码+解码时序测量表明 b85 比 b64 慢 13 倍以上。所以我们有 7% 的规模胜利,但 1300% 的性能损失。DEL
定义为控制字符。