在字符串和 ArrayBuffers 之间转换

IT技术 javascript serialization arraybuffer typed-arrays
2021-01-28 18:44:36

是否有一种普遍接受的技术可以有效地将 JavaScript 字符串转换为ArrayBuffers,反之亦然?具体来说,我希望能够将 ArrayBuffer 的内容写入localStorage并读回。

6个回答

2016更新- 五年后,规范中出现了新方法(请参阅下面的支持),可以使用正确的编码在字符串和类型化数组之间进行转换。

文本编码器

TextEncoder代表

TextEncoder接口表示为具体的方法的编码器,这是一个特定的字符编码,如utf-8iso-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"));

如果需要,您当然可以使用.buffer结果上参数Uint8Array将底层转换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));

MDN StringView 库

另一种方法是使用StringView(许可为 lgpl-3.0),其目标是:

  • 基于 JavaScript ArrayBuffer 接口为字符串(即字符代码数组——JavaScript 中的 ArrayBufferView)创建一个类似 C 的接口
  • 创建一个高度可扩展的库,任何人都可以通过向对象 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
不支持来自 IE 和 Edge 的TextDecodercaniuse.com/#search=TextDecoder
2021-03-10 18:44:36
2018 年 4 月 18 日不支持 Safari Mobile(ios):developer.mozilla.org/en-US/docs/Web/API/TextDecoder
2021-03-13 18:44:36
2021-03-25 18:44:36
单线: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');
2021-03-31 18:44:36
问题TextEncoder在于,如果字符串中有二进制数据(例如图像),则您不想使用TextEncoder(显然)。代码点大于 127 的字符产生两个字节。为什么我的字符串中有二进制数据?cy.fixture(NAME, 'binary')( cypress) 产生一个字符串。
2021-04-05 18:44:36

尽管使用 Blob/FileReader 的 Dennis 和 gengkev 解决方案有效,但我不建议采用这种方法。这是解决简单问题的异步方法,比直接解决方案慢得多。我在 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.comJeff 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>
不幸的是,我对 html5rocks 的评论尚未获得批准。因此,这里有一个简短的回答。我仍然认为,这不是正确的方法,因为您错过了很多字符,尤其是因为当今大多数页面都采用 UTF-8 编码。一方面,对于更多的特殊字符(假设是亚洲字符),charCodeAt 函数返回一个 4 字节的值,因此它们将被切碎。另一方面,简单的英文字符会使 ArrayBuffer 增长两倍(您为每个 1 字节字符使用 2 字节)。想象一下通过 WebSocket 发送英文文本,它需要两次时间(在实时环境中不好)。
2021-03-19 18:44:36
三个例子: (1) This is a cool text!UTF8 中的 20 Byte -- Unicode 中的 40 Byte。(2) ÄÖÜUTF8 中的 6 Bytes -- Unicode 中的 6 Bytes。(3) ☐☑☒UTF8 中的 9 个字节——Unicode 中的 6 个字节。如果要将字符串存储为 UTF8 文件(通过 Blob 和 File Writer API),则不能使用这 2 种方法,因为 ArrayBuffer 将采用 Unicode 而不是 UTF8。
2021-03-19 18:44:36
@Dennis - JS 字符串使用 UCS2,而不是 UTF8(甚至 UTF16) - 这意味着 charCodeAt() 总是返回值 0 -> 65535。任何需要 4 个字节结尾的 UTF-8 代码点都将用代理对表示(参见en.wikipedia .org/wiki/... ) - 即两个单独的 16 位 UCS2 值。
2021-03-21 18:44:36
@jacob - 我相信错误是因为可以传递给 apply() 方法的数组长度有限制。例如String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).length,在 Chrome 中对我有用,但是如果您改用 246301,我会收到您的 RangeError 异常
2021-03-30 18:44:36
我收到一个错误:Uncaught RangeError: Maximum call stack size exceeded。可能是什么问题呢?
2021-04-05 18:44:36

您可以使用TextEncoderTextDecoder来自Encoding 标准,它由stringencoding 库填充,将字符串与 ArrayBuffers 相互转换:

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
抱怨...如果我有一个现有的数组缓冲区,我想将一个字符串写入其中,我想我必须使用 uint8array 并第二次复制它?
2021-03-16 18:44:36
为比奇怪的解决方法好得多的新 API 竖起大拇指!
2021-03-23 18:44:36
顺便说一句,这在 Firefox 中默认可用:developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
2021-03-24 18:44:36
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. 不用了,谢谢。
2021-03-28 18:44:36
这不适用于所有类型的字符。
2021-04-02 18:44:36

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 的大字符串。

我们应该采用这个解决方案。因为这解决了一个比公认的用例多的用例
2021-03-26 18:44:36
我得到:“未捕获的无效:json 解码:这不是 json!”
2021-04-09 18:44:36

根据gengkev的回答,我为这两种方式创建了函数,因为BlobBuilder可以处理String和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"
            }
        )
    }
)
在arrayBuffer2String() 中,您是要调用callback(...) 而不是console.log() 吗?否则回调参数将不再使用。
2021-03-11 18:44:36
BlobBuilder 在较新的浏览器中已弃用。更改new BlobBuilder(); bb.append(buf);new Blob([buf]),将第二个函数中的 ArrayBuffer 通过new UintArray(buf)(或任何适合底层数据类型的方法)转换为 UintArray ,然后摆脱getBlob()调用。最后,为了整洁,将 bb 重命名为 blob,因为它不再是 BlobBuilder。
2021-03-15 18:44:36
这看起来像是要走的路 - 感谢 genkev 和 Dennis。没有同步的方法来实现这一点似乎有点愚蠢,但是你能做什么......
2021-03-26 18:44:36
JavaScript 是单线程的。因此 FileReader 是异步的有两个原因:(1) 它不会在加载(巨大的)文件时阻止其他 JavaScript 的执行(想象一个更复杂的应用程序)和(2)它不会阻止 UI/浏览器(常见问题)带有长时间执行的 JS 代码)。许多 API 是异步的。即使在 XMLHttpRequest 2 中,同步也被删除了。
2021-03-27 18:44:36
我真的希望这对我有用,但是从字符串到 ArrayBuffer 的转换工作不可靠。我正在制作一个具有 256 个值的 ArrayBuffer,并且可以将其转换为长度为 256 的字符串。但是如果我尝试将其转换回一个 ArrayBuffer - 取决于我的初始 ArrayBuffer 的内容 - 我得到了 376 个元素。如果您想尝试重现我的问题,我将我的 ArrayBuffer 视为 Uint8Array 中的 16x16 网格,其值按照a[y * w + x] = (x + y) / 2 * 16; 我尝试的方式计算getBlob("x"),具有许多不同的 mimetypes - 没有运气。
2021-03-31 18:44:36