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


当前回答

以下所有内容都是关于从数组缓冲区中获取二进制字符串

我建议不要用

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。

其他回答

最近,我还需要为我的一个项目做这件事,所以做了一个很好的研究,并从谷歌的开发者社区得到了一个结果,它以简单的方式陈述了这一点:

用于ArrayBuffer to String

function ab2str(buf) {
  return String.fromCharCode.apply(null, new Uint16Array(buf));
}
// Here Uint16 can be different like Uinit8/Uint32 depending upon your buffer value type.

用于字符串到ArrayBuffer

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;
}
//Same here also for the Uint16Array.

欲了解更多详细信息,请参考谷歌的博客。

请看这里:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView (基于JavaScript ArrayBuffer接口的类似c语言的字符串接口)

从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);

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

我也遇到了这个问题,以下是我在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

  stringToArrayBuffer(byteString) {
    var byteArray = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
      byteArray[i] = byteString.codePointAt(i);
    }
    return byteArray;
  }
  arrayBufferToString(buffer) {
    var byteArray = new Uint8Array(buffer);
    var byteString = '';
    for (var i = 0; i < byteArray.byteLength; i++) {
      byteString += String.fromCodePoint(byteArray[i]);
    }
    return byteString;
  }