ChatGPT解决这个技术问题 Extra ChatGPT

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 字符串?

上传:你只做一次,所以没什么大不了的。对于下载,您可能会对 base64 compresses under gzip 的性能感到惊讶,因此如果您在服务器上启用了 gzip,您也可能没问题。
对于铁杆书呆子来说,另一个有价值的解决方案msgpack.orggithub.com/msgpack/msgpack/blob/master/spec.md
@cloudfeet,每个用户每次操作一次。非常大的一笔。
请注意,每个字符通常是 2 字节的内存。因此,base64 可能会在线路上产生 +33% (4/3) 的开销,但将数据放在线路上、检索并利用它,将需要 +166% (8/3 ) 开销。恰当的例子:如果 Javascript 字符串的最大长度为 100k 字符,则使用 base64 只能表示 37.5k 字节的数据,而不是 75k 字节的数据。这些数字可能是应用程序许多部分的瓶颈,例如 JSON.parse 等......
@Pacerier“通常 2 个字节的内存 [每个字符]”不准确。例如 v8 有 OneByte 和 TwoByte 字符串。仅在必要时使用两字节字符串以避免怪异的内存消耗。 Base64 可以用一字节字符串编码。

o
orad

根据 JSON 规范(如果您的 JSON 以 UTF-8 传输),有 94 个 Unicode 字符可以表示为一个字节。考虑到这一点,我认为您可以在空间方面做的最好的事情是 base85 ,它将四个字节表示为五个字符。但是,这仅比 base64 提高了 7%,计算成本更高,并且实现不如 base64 常见,因此它可能不是胜利。

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

最终判决:在我看来,base64 获胜,因为它很常见,很容易,而且还不足以保证更换。

另请参阅:Base91Base122


等等,如何在对引号字符进行 105% 扩展而 base64 仅 33% 时使用实际字节? base64 不是 133% 吗?
Base91 对于 JSON 来说是个坏主意,因为它包含字母表中的引号。在 JSON 编码后的最坏情况下(所有引号输出),它是原始有效负载的 245%。
Python 3.4 现在包括 base64.b85encode()b85decode()。一个简单的编码+解码时序测量表明 b85 比 b64 慢 13 倍以上。所以我们有 7% 的规模胜利,但 1300% 的性能损失。
@hobbs JSON 声明必须对控制字符进行转义。 RFC20 section 5.2DEL 定义为控制字符。
@Tino ECMA-404 专门列出了需要转义的字符:双引号 U+0022、反斜杠 U+005C 和“控制字符 U+0000 到 U+001F”。
S
StayOnTarget

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

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

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

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

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

恕我直言,发送 base64 编码数据是一种黑客行为; RFC multipart/form-data 是针对以下问题创建的:发送二进制数据与文本或元数据。


顺便说一句,Google Drive API 就是这样做的:developers.google.com/drive/v2/reference/files/update#examples
为什么当它使用本机功能而不是尝试将圆形(二进制)钉挤入方形(ASCII)孔时,这个答案如此低?...
发送 base64 编码数据是一种 hack,multipart/form-data 也是如此。即使您链接的博客文章也显示通过使用您声明的 Content-Type multipart/form-data,您发送的实际上是一个表单。但事实并非如此。所以我认为 base64 hack 不仅更容易实现而且更可靠我见过一些库(例如 Python),它们具有硬编码的 multipart/form-data 内容类型。
@t3chb0t multipart/form-data 媒体类型是为传输表单数据而生的,但今天它在 HTTP/HTML 世界之外被广泛使用,特别是用于编码电子邮件内容。今天,它被提议作为一种通用的编码语法。 tools.ietf.org/html/rfc7578
@MarkKCowan 可能是因为虽然这对问题的目的有所帮助,但它并没有回答所问的问题,这实际上是“用于 JSON 的文本编码的低开销二进制文件”,这个答案完全抛弃了 JSON。
K
KenD

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

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


“在某些情况下,由于长度前缀和显式数组索引,BSON 将使用比 JSON 更多的空间。” en.wikipedia.org/wiki/BSON
好消息:BSON 原生支持 Binary、Datetime 和其他一些类型(如果您使用 MongoDB,则特别有用)。坏消息:它的编码是二进制字节......所以它不是对 OP 的回答。但是,它在原生支持二进制的通道(例如 RabbitMQ 消息、ZeroMQ 消息或自定义 TCP 或 UDP 套接字)上会很有用。
c
chmike

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 是最好的。


Base128 怎么样,然后让 JSON 序列化程序转义 " 和 \ ?我认为期望用户使用 json 解析器实现是合理的。
@jcalfee314 不幸的是,这是不可能的,因为 JSON 字符串中不允许使用 ASCII 码低于 32 的字符。已经定义了基数在 64 到 128 之间的编码,但所需的计算量高于 base64。编码文本大小的增益是不值得的。
如果在 base64 中加载大量图像(比如 1000 个),或者通过非常慢的连接加载,base85 或 base93 是否会为减少的网络流量(w/或 w/o gzip)付出代价?我很好奇是否会出现更紧凑的数据会成为其中一种替代方法的理由。
@Pacerier 我的陈述在使用 UTF8 编码时是正确的。所以这不是“完全错误的”。当使用 2 个字节来存储每个字符时,是的,存储大小变为二进制大小的 260%。如您所知,JSON 用于数据存储或传输,在这种情况下使用 UTF8 编码。在这种情况下,这是问题所涉及的,我的评论是正确和中肯的。
但是,我刚刚做了 base64 imgfile | gzip | wc -l,原始图像文件仅增长了几个 %,因此由于 base64 非常简单,gzip 传输几乎是给定的,使用 base64 确实是压缩数据的压缩 JSON 传输的好主意。但是,将未压缩的数据转换为 base64 然后 gzip 产生的字节数比 gzip+base64+gzip 高得多。
C
Community

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

这种魔法的好例子在 http://jszip.stuartk.co.uk/,关于这个主题的更多讨论在 JavaScript implementation of Gzip


这是一个声称性能更好的 JavaScript zip 实现:zip.js
请注意,您也可以(并且应该)在之后进行压缩(通常通过 Content-Encoding),因为 base64 压缩得非常好。
@MahmoudAl-Qudsi 你的意思是你 base64(zip(base64(zip(data))))?我不确定添加另一个 zip 然后 base64 (以便能够将其作为数据发送)是个好主意。
@andrej他的意思是在Web服务器中启用压缩,这显然支持二进制,因此您的代码执行 base64(zip(data)) 但客户端或服务器在通过(二进制)线路发送之前对 ASCII 进行压缩,另一个end 在将其交给接收 ASCII 的接收器代码之前解压缩并解压缩(decode64(received))
@android.weasel AFAIK 服务器端压缩仅压缩服务器输出
S
StayOnTarget

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 更糟糕。


由于很多人似乎仍在查看这个问题,我想提一下,我认为 yEnc 在这里并没有真正的帮助。 yEnc 是一种 8 位编码,因此将其存储在 JSON 字符串中与存储原始二进制数据具有相同的问题——以天真的方式进行存储意味着大约 100% 的扩展,这比 base64 更糟糕。
如果使用带有 JSON 数据的大字母的 yEnc 等编码被认为是可以接受的,escapeless 可以作为一个很好的替代方案,提供固定的预先已知开销。
S
StaxMan

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

我同意之前的两个答案:

base64 是简单的常用标准,因此不太可能找到更好的专门用于 JSON 的东西(postscript 等使用 base-85;但考虑到它的好处充其量是微不足道的)

编码前(和解码后)的压缩可能很有意义,具体取决于您使用的数据


M
Mahmoud Al-Qudsi

Smile format

编码、解码和压缩都非常快

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

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

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


...并且链接已失效。这个似乎是最新的:github.com/FasterXML/smile-format-specification
这就是为什么添加指向答案的链接是一个不好的举动.. 至少在答案中添加一个有用的片段:-)
j
jsoverson

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


S
StayOnTarget

(7 年后编辑:Google Gears 不见了。忽略这个答案。)

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

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

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


那么 Javascript 和 json 中 blob 的状态是什么?它被丢弃了吗?
w3.org/TR/FileAPI/#blob-section 对于空间而言,性能不如 base64,如果您向下滚动,您会发现它使用 utf8 映射进行编码(霍布斯的回答中显示的选项之一)。据我所知,没有 json 支持
Z
Zub

只是为了在讨论中添加资源和复杂性的观点。由于执行 PUT/POST 和 PATCH 是为了存储和更改新资源,因此应该记住内容传输是存储内容的精确表示,并且通过发出 GET 操作接收。

多部分消息通常被用作救星,但出于简单原因和更复杂的任务,我更喜欢将内容作为一个整体提供的想法。这是不言自明的,而且很简单。

是的,JSON 是很糟糕的东西,但最终 JSON 本身是冗长的。而且映射到 BASE64 的开销是一种很小的方式。

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

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

基本上,我们只是在这里遗漏了一些东西,但是将二进制数据嵌入为 base64 已经很成熟了,除非你真的确定需要进行真正的二进制传输(这种情况很少见)。


在 .NET 中发送和接收多部分消息并不有趣,过于复杂和抽象。只发送原始字符串更容易,因此您可以实际调试并查看发送和接收的内容,并将字符串转换为服务器上的 JSON 对象或类对象。 JSON 或 XML 字符串中的 Base64 易于调试
s
shrewmouse

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

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

是不是更有效率?不。

它是一个完善的标准吗?是的。

它像 JSON 一样可读吗?是的,比大多数二进制解决方案更具可读性。

json 看起来像:

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

效率低吗?是的。
我们知道它的空间效率较低。时间效率低吗?它绝对是更具人类可读性的高效。
R
Richie Bendall

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

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

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


测试和认可?
如果您想要 100% 的可靠性,请将“binary”替换为“base64”
K
Kamil Kiełczewski

深入

我再挖掘一点(在 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); }


K
Koushik

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


D
Dheeraj Sangamkar

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

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

如果您可以使用“非 CDMI 内容类型”操作,那么将“数据”传输到/从对象传输是理想的。元数据随后可以作为后续的“CDMI 内容类型”操作添加到对象/从对象中检索。


A
Atlas Sullivan

另一个更新颖的想法是通过 uuencode 对数据进行编码。这是一个主要被弃用的,但它可能仍然是一个替代方案。 (虽然可能不是一个严重的问题。)


R
Rolf Rost

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

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


您将通过序列化字节数组使您的数据增长 +100%
@Sharcoux wot??
JSON 中字节数组的序列化类似于:[16, 2, 38, 89],效率非常低。