是否有一种普遍接受的技术可以有效地将JavaScript字符串转换为arraybuffer,反之亦然?具体来说,我希望能够将ArrayBuffer的内容写入localStorage,然后再将其读回来。
当前回答
2016年更新——五年过去了,现在规范中有了新的方法(参见下面的支持),可以使用适当的编码在字符串和类型化数组之间进行转换。
TextEncoder
TextEncoder表示:
TextEncoder接口表示特定方法的编码器, 这是一种特定的字符编码,如utf-8, iso-8859-2, koi8, Cp1261, gbk,…编码器以码点流作为输入 发出一个字节流。
自写上篇以来的变动注:(同上)
注意:Firefox, Chrome和Opera曾经支持编码 utf-8以外的类型(如utf-16、iso-8859-2、koi8、cp1261和 gbk)。Firefox 48[…]], Chrome 54[…]Opera 41,没有 为了匹配,除了utf-8,还有其他编码类型可用 规范。*
*)更新规格(W3)和这里(whatwg)。
在创建TextEncoder实例后,它将接受一个字符串并使用给定的编码参数对其进行编码:
如果(!("TextEncoder"在窗口)) alert(“对不起,这个浏览器不支持TextEncoder…”); var enc = new TextEncoder();// always utf-8 console.log(内附。encode("这是一个转换为Uint8Array的字符串"));
当然,如果需要的话,你可以在结果的Uint8Array上使用.buffer参数来将底层的ArrayBuffer转换为不同的视图。
只需确保字符串中的字符符合编码模式,例如,如果在示例中使用UTF-8范围之外的字符,它们将被编码为两个字节而不是一个字节。
一般情况下,你可以使用UTF-16编码来处理localStorage之类的东西。
TextDecoder
同样,相反的进程使用TextDecoder:
TextDecoder接口表示特定方法的解码器, 这是一种特定的字符编码,如utf-8, iso-8859-2, koi8, Cp1261, gbk,…解码器接受字节流作为输入并发出 代码点流。
所有可用的解码类型都可以在这里找到。
如果(!(“TextDecoder”在窗口)) alert(“抱歉,这个浏览器不支持TextDecoder…”); var enc = new TextDecoder("utf-8"); var arr = new Uint8Array([84,104,105,115,32,105,115,32,97,32,85,105,110,116, 56、65114114、97121、99111110118101114116, 101100, 32116111, 97, 32115116114105110103]); console.log (enc.decode (arr));
MDN StringView库
另一种选择是使用StringView库(许可为lgpl-3.0),其目标是:
to create a C-like interface for strings (i.e., an array of character codes — an ArrayBufferView in JavaScript) based upon the JavaScript ArrayBuffer interface to create a highly extensible library that anyone can extend by adding methods to the object StringView.prototype to create a collection of methods for such string-like objects (since now: stringViews) which work strictly on arrays of numbers rather than on creating new immutable JavaScript strings to work with Unicode encodings other than JavaScript's default UTF-16 DOMStrings
给予更多的灵活性。然而,它需要我们链接到或嵌入这个库,而TextEncoder/TextDecoder是内置在现代浏览器中。
支持
截至2018年7月:
TextEncoder(实验性,在标准轨道上)
Chrome | Edge | Firefox | IE | Opera | Safari
----------|-----------|-----------|-----------|-----------|-----------
38 | ? | 19° | - | 25 | -
Chrome/A | Edge/mob | Firefox/A | Opera/A |Safari/iOS | Webview/A
----------|-----------|-----------|-----------|-----------|-----------
38 | ? | 19° | ? | - | 38
°) 18: Firefox 18 implemented an earlier and slightly different version
of the specification.
WEB WORKER SUPPORT:
Experimental, On Standard Track
Chrome | Edge | Firefox | IE | Opera | Safari
----------|-----------|-----------|-----------|-----------|-----------
38 | ? | 20 | - | 25 | -
Chrome/A | Edge/mob | Firefox/A | Opera/A |Safari/iOS | Webview/A
----------|-----------|-----------|-----------|-----------|-----------
38 | ? | 20 | ? | - | 38
Data from MDN - `npm i -g mdncomp` by epistemex
其他回答
atob()返回的“本机”二进制字符串是一个每个字符1字节的数组。
所以我们不应该在一个字符中存储2个字节。
var arrayBufferToString = function(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
var stringToArrayBuffer = function(str) {
return (new Uint8Array([].map.call(str,function(x){return x.charCodeAt(0)}))).buffer;
}
从emscripten:
function stringToUTF8Array(str, outU8Array, outIdx, maxBytesToWrite) {
if (!(maxBytesToWrite > 0)) return 0;
var startIdx = outIdx;
var endIdx = outIdx + maxBytesToWrite - 1;
for (var i = 0; i < str.length; ++i) {
var u = str.charCodeAt(i);
if (u >= 55296 && u <= 57343) {
var u1 = str.charCodeAt(++i);
u = 65536 + ((u & 1023) << 10) | u1 & 1023
}
if (u <= 127) {
if (outIdx >= endIdx) break;
outU8Array[outIdx++] = u
} else if (u <= 2047) {
if (outIdx + 1 >= endIdx) break;
outU8Array[outIdx++] = 192 | u >> 6;
outU8Array[outIdx++] = 128 | u & 63
} else if (u <= 65535) {
if (outIdx + 2 >= endIdx) break;
outU8Array[outIdx++] = 224 | u >> 12;
outU8Array[outIdx++] = 128 | u >> 6 & 63;
outU8Array[outIdx++] = 128 | u & 63
} else {
if (outIdx + 3 >= endIdx) break;
outU8Array[outIdx++] = 240 | u >> 18;
outU8Array[outIdx++] = 128 | u >> 12 & 63;
outU8Array[outIdx++] = 128 | u >> 6 & 63;
outU8Array[outIdx++] = 128 | u & 63
}
}
outU8Array[outIdx] = 0;
return outIdx - startIdx
}
使用:
stringToUTF8Array('abs', new Uint8Array(3), 0, 4);
我不建议使用已弃用的api,如BlobBuilder
Blob对象早已弃用了BlobBuilder。将Dennis回答中的代码(其中使用了BlobBuilder)与下面的代码进行比较:
function arrayBufferGen(str, cb) {
var b = new Blob([str]);
var f = new FileReader();
f.onload = function(e) {
cb(e.target.result);
}
f.readAsArrayBuffer(b);
}
请注意,与已弃用的方法相比,这是多么干净和不那么臃肿……是的,这绝对是需要考虑的。
Blob比String.fromCharCode(null,array)慢得多;
但如果数组缓冲区太大,就会失败。我发现的最佳解决方案是使用String.fromCharCode(null,数组);并将其拆分为不会破坏堆栈的操作,但每次比单个char更快。
大数组缓冲区的最佳解决方案是:
function arrayBufferToString(buffer){
var bufView = new Uint16Array(buffer);
var length = bufView.length;
var result = '';
var addition = Math.pow(2,16)-1;
for(var i = 0;i<length;i+=addition){
if(i + addition > length){
addition = length - i;
}
result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition));
}
return result;
}
我发现这比使用blob快20倍。它也适用于超过100mb的大字符串。
以下所有内容都是关于从数组缓冲区中获取二进制字符串
我建议不要用
var binaryString = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer));
因为它
大缓冲区崩溃(有人写了关于246300的“神奇”大小,但我得到的最大调用堆栈大小超过了120000字节缓冲区的错误(Chrome 29)) 它的性能真的很差(见下文)
如果您确实需要同步解决方案,请使用类似
var
binaryString = '',
bytes = new Uint8Array(arrayBuffer),
length = bytes.length;
for (var i = 0; i < length; i++) {
binaryString += String.fromCharCode(bytes[i]);
}
它和前一个一样慢,但工作正常。在写这篇文章的时候,似乎还没有针对这个问题的快速同步解决方案(本主题中提到的所有库都使用相同的方法来实现它们的同步特性)。
但我真正推荐的是使用Blob + FileReader方法
function readBinaryStringFromArrayBuffer (arrayBuffer, onSuccess, onFail) {
var reader = new FileReader();
reader.onload = function (event) {
onSuccess(event.target.result);
};
reader.onerror = function (event) {
onFail(event.target.error);
};
reader.readAsBinaryString(new Blob([ arrayBuffer ],
{ type: 'application/octet-stream' }));
}
唯一的缺点(并非所有缺点)是它是异步的。它比以前的解决方案快8-10倍!(一些细节:在我的环境中,同步解决方案需要950-1050 ms才能获得2.4Mb的缓冲区,而使用FileReader的解决方案需要大约100-120 ms才能获得相同数量的数据。我已经在100Kb缓冲区上测试了这两种同步解决方案,它们几乎花费了相同的时间,所以使用'apply'的循环并不会慢很多。)
BTW在这里:如何转换ArrayBuffer和字符串作者比较两种方法像我和得到完全相反的结果(他的测试代码在这里)为什么这么不同的结果?可能是因为他的测试字符串有1Kb长(他称之为“veryLongStr”)。我的缓冲区是一张非常大的JPEG图像,大小为2.4Mb。