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


当前回答

尽管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;
}

编辑:

Encoding API帮助解决字符串转换问题。请查看Jeff Posnik在Html5Rocks.com上对上述原创文章的回复。

摘录:

Encoding 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>

其他回答

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

与这里的解决方案不同,我需要从UTF-8数据转换到UTF-8数据。为此,我使用(un)escape/(en)decodeURIComponent技巧编写了以下两个函数。它们非常浪费内存,分配的长度是编码后utf8-string的9倍,尽管这些应该由gc恢复。只是不要在100mb的文本中使用它们。

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кирилицаαβγδεζηあいうえお"

对我来说,这很有效。

  static async hash(message) {
    const data = new TextEncoder().encode(message);
    const hashBuffer = await crypto.subtle.digest('SHA-256', data)
    const hashArray = Array.from(new Uint8Array(hashBuffer))
    const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
    return hashHex
  }

(更新请参阅这个答案的后半部分,我(希望)提供了一个更完整的解决方案。)

我也遇到了这个问题,以下是我在FF 6中的工作(一个方向):

var buf = new ArrayBuffer( 10 );
var view = new Uint8Array( buf );
view[ 3 ] = 4;
alert(Array.prototype.slice.call(view).join(""));

当然,不幸的是,您最终得到的是数组中值的ASCII文本表示,而不是字符。尽管如此,它仍然(应该)比循环更有效。 如。对于上面的例子,结果是0004000000,而不是几个空字符&一个chr(4)。

编辑:

看完这里的MDC,你可以从一个数组创建一个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,在那里字符串可能存储在eg. . localstorage:)

希望这能有所帮助,

Dan

在使用了mangini的从ArrayBuffer转换到String的解决方案- ab2str(这是我发现的最优雅和有用的一个-谢谢!)之后,我在处理大型数组时遇到了一些问题。更具体地说,调用String.fromCharCode.apply(null, new Uint16Array(buf));抛出错误:

传递给Function.prototype.apply的参数数组太大。

为了解决它(绕过),我决定处理输入ArrayBuffer块。所以修改后的解是:

function ab2str(buf) {
   var str = "";
   var ab = new Uint16Array(buf);
   var abLen = ab.length;
   var CHUNK_SIZE = Math.pow(2, 16);
   var offset, len, subab;
   for (offset = 0; offset < abLen; offset += CHUNK_SIZE) {
      len = Math.min(CHUNK_SIZE, abLen-offset);
      subab = ab.subarray(offset, offset+len);
      str += String.fromCharCode.apply(null, subab);
   }
   return str;
}

块大小设置为2^16,因为这是我发现在我的开发环境中工作的大小。设置一个更高的值会导致同样的错误再次发生。可以通过将CHUNK_SIZE变量设置为不同的值来更改它。偶数是很重要的。

性能注意事项—我没有对此解决方案进行任何性能测试。但是,由于它基于前面的解决方案,并且可以处理大型数组,所以我认为没有理由不使用它。