是否有一种通常被接受的技术,能够有效地将JavaScript字符串转换为ArrayBuffers,反之亦然?具体而言,我希望能够将一个ArrayBuffer的内容写入localStorage
中,并随后读取。
是否有一种通常被接受的技术,能够有效地将JavaScript字符串转换为ArrayBuffers,反之亦然?具体而言,我希望能够将一个ArrayBuffer的内容写入localStorage
中,并随后读取。
2016年更新 - 经过五年的发展,现在有新的方法(请参见下面的支持)在规范中用于使用适当的编码在字符串和类型化数组之间进行转换。
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
的实例后,它将使用给定的编码参数对字符串进行编码:
if (!("TextEncoder" in window))
alert("Sorry, this browser does not support TextEncoder...");
var enc = new TextEncoder(); // always utf-8
console.log(enc.encode("This is a string converted to a Uint8Array"));
然后你当然可以使用结果为Uint8Array
的.buffer
参数将底层的ArrayBuffer
转换为不同的视图(如果需要的话)。
只需确保字符串中的字符符合编码模式,例如,在示例中使用UTF-8范围之外的字符会将其编码为两个字节而不是一个字节。
对于一般用途,您应该使用UTF-16编码,例如localStorage
。
同样,相反的过程使用 TextDecoder
:
TextDecoder
接口表示特定方法的解码器,即特定的字符编码,如utf-8
、iso-8859-2
、koi8
、cp1261
、gbk
等。解码器将字节流作为输入,并发出代码点流。
所有可用的解码类型可以在此处找到。
if (!("TextDecoder" in window))
alert("Sorry, this browser does not support 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,65,114,114,97,121,32,99,111,110,118,101,114,116,
101,100,32,116,111,32,97,32,115,116,114,105,110,103]);
console.log(enc.decode(arr));
除了使用现有的TextEncoder
/TextDecoder
之外,还可以使用StringView库(采用lgpl-3.0许可证),其目标是:
- 创建基于JavaScript ArrayBuffer接口的字符串的类C接口(即字符代码数组 - 在JavaScript中为ArrayBufferView)
- 创建一个高度可扩展的库,任何人都可以通过向对象StringView.prototype添加方法来扩展它
- 创建一组方法,针对这些类似于字符串的对象(从现在开始:stringViews),这些方法严格地处理数字数组而不是创建新的不可变JavaScript字符串
- 使用JavaScript默认的UTF-16 DOMStrings以外的Unicode编码
这样可以提供更多的灵活性。 但是,它需要我们链接或嵌入此库,而现代浏览器中已内置了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
var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};
这样你就可以使用 var array = encoder.encode('hello');
- YetiTextEncoder
的问题在于,如果您在字符串中有二进制数据(例如图片),则不应使用TextEncoder
(显然)。代码点大于127的字符会产生两个字节。为什么我在字符串中有二进制数据?cy.fixture(NAME, 'binary')
(cypress
)会生成一个字符串。 - x-yurinew TextDecoder("encoding")
外面加上 try/catch,因为你可能支持 TextDecoder 但不支持你想要使用的字符编码。 - PHP Guru尽管Dennis和gengkev使用Blob/FileReader方法的解决方案可以工作,但我不建议采用这种方法。这是一个将异步方法应用于简单问题的方法,速度比直接解决方案慢得多。我在html5rocks上发布了一篇文章,介绍了一种更简单(并且更快)的解决方案:
http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
而该解决方案如下:
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint16Array(buf));
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i=0, strLen=str.length; i<strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
编辑:
编码API有助于解决字符串转换问题。请查看来自Html5Rocks.com的Jeff Posnik对上述原始文章的回应。
摘录:
无论您需要使用哪种标准编码方式,编码API都可以轻松地在原始字节和本机JavaScript字符串之间进行转换。
<pre id="results"></pre>
<script>
if ('TextDecoder' in window) {
// The local files to be fetched, mapped to the encoding that they're using.
var filesToEncoding = {
'utf8.bin': 'utf-8',
'utf16le.bin': 'utf-16le',
'macintosh.bin': 'macintosh'
};
Object.keys(filesToEncoding).forEach(function(file) {
fetchAndDecode(file, filesToEncoding[file]);
});
} else {
document.querySelector('#results').textContent = 'Your browser does not support the Encoding API.'
}
// Use XHR to fetch `file` and interpret its contents as being encoded with `encoding`.
function fetchAndDecode(file, encoding) {
var xhr = new XMLHttpRequest();
xhr.open('GET', file);
// Using 'arraybuffer' as the responseType ensures that the raw data is returned,
// rather than letting XMLHttpRequest decode the data first.
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
if (this.status == 200) {
// The decode() method takes a DataView as a parameter, which is a wrapper on top of the ArrayBuffer.
var dataView = new DataView(this.response);
// The TextDecoder interface is documented at http://encoding.spec.whatwg.org/#interface-textdecoder
var decoder = new TextDecoder(encoding);
var decodedString = decoder.decode(dataView);
// Add the decoded file's text to the <pre> element on the page.
document.querySelector('#results').textContent += decodedString + '\n';
} else {
console.error('Error while requesting', file, this);
}
};
xhr.send();
}
</script>
String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).length
这个例子在Chrome中可以运行,但如果你将数组长度改为246301,就会出现RangeError异常。 - broofaTextEncoder
和TextDecoder
,这些功能由stringencoding库提供支持,将字符串转换为ArrayBuffers并相互转换:var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
Blob速度比String.fromCharCode(null, array);
慢得多。
但如果数组缓冲区太大,String.fromCharCode(null, array);
会失败。我发现最好的解决方法是将它分割成不会耗尽堆栈但速度比一次处理一个字符更快的操作。
对于较大的数组缓冲区,最好的解决方案是:
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 的大字符串。
如果你在字符串中有二进制数据(从nodejs
+readFile(..., 'binary')
或cypress
+cy.fixture(..., 'binary')
等获取),那么你不能使用TextEncoder
。它只支持utf8
。具有值>= 128
的字节会被转换成2个字节。
ES2015:
a = Uint8Array.from(s, x => x.charCodeAt(0))
Uint8Array(33) [2, 134, 140, 186, 82, 70, 108, 182, 233, 40, 143, 247, 29, 76, 245, 206, 29, 87, 48, 160, 78, 225, 242, 56, 236, 201, 80, 80, 152, 118, 92, 144, 48
这是一个长度为33的Uint8Array数组,其中包含33个8位无符号整数。每个整数都在0到255之间。s = String.fromCharCode.apply(null, a)
"ºRFl¶é(÷LõÎW0 Náò8ìÉPPv\0"
fromCharCode
能够在TypeScript中使用?它会报错说a
不是一个number[]
。 - philk根据gengkev的答案,我创建了两种方式的函数,因为BlobBuilder可以处理字符串和ArrayBuffer:
function string2ArrayBuffer(string, callback) {
var bb = new BlobBuilder();
bb.append(string);
var f = new FileReader();
f.onload = function(e) {
callback(e.target.result);
}
f.readAsArrayBuffer(bb.getBlob());
}
并且
function arrayBuffer2String(buf, callback) {
var bb = new BlobBuilder();
bb.append(buf);
var f = new FileReader();
f.onload = function(e) {
callback(e.target.result)
}
f.readAsText(bb.getBlob());
}
一个简单的测试:
string2ArrayBuffer("abc",
function (buf) {
var uInt8 = new Uint8Array(buf);
console.log(uInt8); // Returns `Uint8Array { 0=97, 1=98, 2=99}`
arrayBuffer2String(buf,
function (string) {
console.log(string); // returns "abc"
}
)
}
)
a[y * w + x] = (x + y) / 2 * 16;
。我已经尝试了许多不同的MIME类型使用getBlob("x")
,但没有运气。 - Matt Cruikshanknew BlobBuilder(); bb.append(buf);
替换为new Blob([buf])
,在第二个函数中将ArrayBuffer转换为UintArray,方法是使用new UintArray(buf)
(或者适用于底层数据类型的其他方法),然后消除getBlob()
调用。最后,为了清晰明了,将bb重命名为blob,因为它不再是BlobBuilder。 - sowbugconst buffer = thisReturnsBuffers();
const blob = new Blob([buffer], {type: 'text/plain; charset=utf-8'});
blob.text().then(text => console.log(text));
或者
const stringVal = "string here";
const blob = new Blob([stringVal], {type: 'text/plain; charset=utf-8'});
blob.arrayBuffer().then(buffer => console.log(buffer));
你们为什么要把这件事情搞得这么复杂呢?
我建议不要使用
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倍!(一些细节:在我的环境中,同步解决方案对于2.4Mb缓冲区需要950-1050毫秒,但FileReader的解决方案对于相同数量的数据只需要100-120毫秒。我已经测试了两种同步解决方案在100Kb缓冲区上,它们所花费的时间几乎相同,因此使用“apply”循环不比它们慢多少。)(更新 请查看本答案的下半部分,我已经(希望)提供了一个更完整的解决方案。)
我也遇到了这个问题,在FF6中以下方法适用于我(仅适用于一个方向):
var buf = new ArrayBuffer( 10 );
var view = new Uint8Array( buf );
view[ 3 ] = 4;
alert(Array.prototype.slice.call(view).join(""));
很不幸地,你最终得到的是数组中值的ASCII文本表示,而不是字符。但这种方法仍然比循环更有效率。
例如,对于上面的示例,结果为0004000000
,而不是几个null字符和chr(4)
。
编辑:
在查看MDC 这里 后,你可以按以下方式从Array
创建一个ArrayBuffer
:
var arr = new Array(23);
// New Uint8Array() converts the Array elements
// to Uint8s & creates a new ArrayBuffer
// to store them in & a corresponding view.
// To get at the generated ArrayBuffer,
// you can then access it as below, with the .buffer property
var buf = new Uint8Array( arr ).buffer;
回答你的最初问题,这使你可以将ArrayBuffer
与String
相互转换:
var buf, view, str;
buf = new ArrayBuffer( 256 );
view = new Uint8Array( buf );
view[ 0 ] = 7; // Some dummy values
view[ 2 ] = 4;
// ...
// 1. Buffer -> String (as byte array "list")
str = bufferToString(buf);
alert(str); // Alerts "7,0,4,..."
// 1. String (as byte array) -> Buffer
buf = stringToBuffer(str);
alert(new Uint8Array( buf )[ 2 ]); // Alerts "4"
// Converts any ArrayBuffer to a string
// (a comma-separated list of ASCII ordinals,
// NOT a string of characters from the ordinals
// in the buffer elements)
function bufferToString( buf ) {
var view = new Uint8Array( buf );
return Array.prototype.join.call(view, ",");
}
// Converts a comma-separated ASCII ordinal string list
// back to an ArrayBuffer (see note for bufferToString())
function stringToBuffer( str ) {
var arr = str.split(",")
, view = new Uint8Array( arr );
return view.buffer;
}
为了方便,这里提供一个函数
,用于将原始的Unicode 字符串
转换为ArrayBuffer
(仅适用于ASCII / 单字节字符)
function rawStringToBuffer( str ) {
var idx, len = str.length, arr = new Array( len );
for ( idx = 0 ; idx < len ; ++idx ) {
arr[ idx ] = str.charCodeAt(idx) & 0xFF;
}
// You may create an ArrayBuffer from a standard array (of values) as follows:
return new Uint8Array( arr ).buffer;
}
// Alerts "97"
alert(new Uint8Array( rawStringToBuffer("abc") )[ 0 ]);
上述方法允许您将ArrayBuffer
转换为String
,并再次转换回ArrayBuffer
,其中字符串可以存储在例如.localStorage
中。
希望这可以帮助到您,
Dan
function utf8AbFromStr(str) {
var strUtf8 = unescape(encodeURIComponent(str));
var ab = new Uint8Array(strUtf8.length);
for (var i = 0; i < strUtf8.length; i++) {
ab[i] = strUtf8.charCodeAt(i);
}
return ab;
}
function strFromUtf8Ab(ab) {
return decodeURIComponent(escape(String.fromCharCode.apply(null, ab)));
}
检查它是否正常工作:
strFromUtf8Ab(utf8AbFromStr('latinкирилицаαβγδεζηあいうえお'))
-> "latinкирилицаαβγδεζηあいうえお"
ArrayBufferView
,可能可以简单地使用括号表示法来复制字符string[i] = buffer[i]
,反之亦然。 - FK82Uint16Array
来处理JS的16位字符),但JavaScript字符串是不可变的,所以您不能直接赋值给一个字符位置。我仍然需要将Uint16Array
中每个值的String.fromCharCode(x)
复制到普通的Array
中,然后在Array
上调用.join()
。 - kpozinstring += String.fromCharCode(buffer[i]);
更加高效。没有内置方法在字符串和类型数组之间转换似乎很奇怪。他们本应该知道会出现这样的情况。翻译:发现大多数现代JavaScript引擎已经将字符串拼接优化到一个程度,在使用“string += String.fromCharCode(buffer[i])”时比其他方式更为便宜。没有内置的方法可以在字符串和类型数组之间进行转换,这似乎有点奇怪,因为他们应该知道这种情况会出现。 - Erin