JSON字符串中的二进制数据。比Base64更好的东西
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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字符串?
根据JSON规范,有94个Unicode字符可以表示为一个字节(如果您的JSON以UTF-8格式传输)。考虑到这一点,我认为你可以在空间方面做的最好的是base85,它代表四个字节为五个字符。然而,这比base64仅提高了7%,计算成本更高,并且实现不如base64那么常见,因此它可能不是一个胜利。
您也可以简单地将每个输入字节映射到U + 0000-U + 00FF中的相应字符,然后执行JSON标准所要求的最小编码来传递这些字符;这里的优点是所需的解码不超过内置函数,但空间效率很差 - 105%扩展(如果所有输入字节都相同),而base85为25%,base64为33%。
最终裁决:在我看来,base64胜利,理由是它是常见的,简单的,并且不足以保证更换。
另见:Base91
由于您正在寻找能够将二进制数据机制化为严格基于文本且非常有限的格式的能力,我认为与您期望使用JSON维护的便利性相比,Base64的开销很小。如果需要考虑处理能力和吞吐量,那么您可能需要重新考虑文件格式。
只是为了讨论增加资源和复杂性的立场。由于做PUT / POST和PATCH用于存储新资源并改变它们,应该记住内容传输是存储的内容的精确表示,并且通过发出GET操作来接收。
多部分消息通常用作救世主,但出于简单原因和更复杂的任务,我更喜欢将内容作为一个整体给出的想法。这是自我解释,很简单。
是的,JSON是令人沮丧的东西,但最终JSON本身就是冗长的。映射到BASE64的开销很小。
正确使用多部分消息,必须拆除要发送的对象,使用属性路径作为自动组合的参数名称,或者需要创建另一个协议/格式来表示有效负载。
也喜欢BSON方法,这并不像人们希望的那样广泛和容易地支持。
基本上,我们只是错过了一些东西,但嵌入二进制数据作为base64已经很好地建立起来,除非你确实已经确定需要进行真正的二进制传输(这种情况很少见)。
数据类型确实令人担忧我已经测试了从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内容类型”操作添加到对象/从对象中检索元数据。
如果你正在使用Node,我认为最有效和最简单的方法是转换为UTF16:
Buffer.from(data).toString('utf16le');
您可以通过以下方式获取数据:
Buffer.from(s, 'utf16le');
我挖了一点(在实施base128期间),并揭示当我们发送ascii代码大于128的字符时,浏览器(chrome)实际上发送两个字符(字节)而不是一个:(。原因是JSON通过默认使用utf8字符,其中ascii代码大于127的字符由chmike回答提到的两个字节编码。我以这种方式进行测试:输入chrome url bar chrome:// net-export /,选择“包含raw bytes“,开始捕获,发送POST请求(使用底部的代码片段),停止捕获并保存带有原始请求数据的json文件。然后我们查看json文件:
- 我们可以通过查找字符串
4142434445464748494a4b4c4d4e
找到我们的base64请求,这是ABCDEFGHIJKLMN
的十六进制编码,我们将看到它的"byte_count": 639
。 - 我们可以通过查找字符串
C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38B
找到我们的127以上请求这是请求 - 十六进制utf8字符¼½ÀÁÂÃÄÅÆÇÈÉÊË
代码(但是这个字符的ascii十六进制代码是c1c2c3c4c5c6c7c8c9cacbcccdce
)。"byte_count": 703
所以它比base64请求长64bytes,因为ascii代码大于127的字符是请求中的2个字节的代码:(
所以事实上我们没有利用发送代码> 127的字符:(。对于base64字符串,我们没有观察到这样的负面行为(可能对于base85而言 - 我不检查它) - 但是可能是这个问题的一些解决方案将是在Ælex answer中描述的POST multipart / form-data的二进制部分中发送数据(但是通常在这种情况下我们根本不需要使用任何基本编码......)。
替代方法可能依赖于使用类似base65280 / base65k的代码将两个字节数据部分映射到一个有效的utf8字符,但由于utf8 specification,它可能不如base64有效...
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>
我的解决方案现在,XHR2正在使用ArrayBuffer。 ArrayBuffer作为二进制序列包含多部分内容,视频,音频,图形,文本等,具有多种内容类型。一体化响应。
在现代浏览器中,为不同的组件提供DataView,StringView和Blob。另请参阅:http://rolfrost.de/video.html了解更多详情。
我遇到了同样的问题,并认为我会分享一个解决方案:multipart / form-data。
通过发送多部分表单,您首先将您的JSON元数据作为字符串发送,然后分别发送为由Content-Disposition名称索引的原始二进制文件(图像,wavs等)。
这是一个很好的tutorial,关于如何在obj-c中执行此操作,这里是a blog article,它解释了如何使用表单边界对字符串数据进行分区,并将其与二进制数据分开。
您真正需要做的唯一改变是在服务器端;您必须捕获应该适当引用POST的二进制数据的元数据(通过使用Content-Disposition边界)。
当然,它需要在服务器端进行额外的工作,但如果您要发送许多图像或大图像,这是值得的。如果需要,可以将其与gzip压缩相结合。
恕我直言,发送base64编码数据是一个黑客;为这样的问题创建了RFC multipart / form-data:结合文本或元数据发送二进制数据。
BSON(二进制JSON)可能适合您。 http://en.wikipedia.org/wiki/BSON
编辑:FYI .NET库json.net支持读写bson,如果你正在寻找一些C#服务器端爱。
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是最好的。
如果您处理带宽问题,请先尝试在客户端压缩数据,然后再使用base64-it。
这种魔术的好例子是在http://jszip.stuartk.co.uk/,关于这个话题的更多讨论是在JavaScript implementation of Gzip
yEnc可能适合你:
http://en.wikipedia.org/wiki/Yenc
“yEnc是一种用于在[text]中传输二进制文件的二进制文本编码方案。它通过使用8位扩展ASCII编码方法减少了以前基于US-ASCII的编码方法的开销.yEnc的开销通常是(如果对于像uuencode和Base64这样的6位编码方法,每个字节值大约平均显示频率相同,只有1-2%,相比之下,6%编码方法的开销为33%-40%......到2003年,yEnc成为事实上的标准Usenet上二进制文件的编码系统。“
但是,yEnc是一个8位编码,因此将其存储在JSON字符串中与存储原始二进制数据具有相同的问题 - 以天真的方式执行它意味着100%的扩展,这比base64更糟糕。
编码,解码和压缩速度非常快
以上是关于JSON字符串中的二进制数据。比Base64更好的东西的主要内容,如果未能解决你的问题,请参考以下文章