在字符串和ArrayBuffer之间进行转换

466

是否有一种通常被接受的技术,能够有效地将JavaScript字符串转换为ArrayBuffers,反之亦然?具体而言,我希望能够将一个ArrayBuffer的内容写入localStorage中,并随后读取。


1
我在这方面没有任何经验,但根据API文档(http://www.khronos.org/registry/typedarray/specs/latest/)的判断,如果您构建一个`Int8Array` ArrayBufferView,可能可以简单地使用括号表示法来复制字符string[i] = buffer[i],反之亦然。 - FK82
2
@FK82,这看起来是一个合理的方法(使用Uint16Array来处理JS的16位字符),但JavaScript字符串是不可变的,所以您不能直接赋值给一个字符位置。我仍然需要将Uint16Array中每个值的String.fromCharCode(x)复制到普通的Array中,然后在Array上调用.join() - kpozin
7
原文:@kpozin 发现大多数现代 JS 引擎已经优化了字符串拼接,以至于仅使用string += String.fromCharCode(buffer[i]);更加高效。没有内置方法在字符串和类型数组之间转换似乎很奇怪。他们本应该知道会出现这样的情况。翻译:发现大多数现代JavaScript引擎已经将字符串拼接优化到一个程度,在使用“string += String.fromCharCode(buffer[i])”时比其他方式更为便宜。没有内置的方法可以在字符串和类型数组之间进行转换,这似乎有点奇怪,因为他们应该知道这种情况会出现。 - Erin
1
arrayBuffer.toString() 对我来说运行良好。 - citizen conn
1
@citizen conn - 我不知道你使用的浏览器是什么,但在 Chrome 上,arrayBuffer.toString() 只会返回 "[object ArrayBuffer]"。并不是很有帮助。 - mrec
显示剩余4条评论
29个回答

3

ArrayBuffer -> Buffer -> String(Base64)

ArrayBuffer 转化为 Buffer,再转化为 Base64 编码的字符串

Buffer.from(arrBuffer).toString("base64");

2
有任何方法可以转换回去吗? - Maximillian Laumeister
@MaximillianLaumeister 是的,请参考cancerbero的答案:https://dev59.com/fWw05IYBdhLWcg3w41wp#57834448。惊讶于在这里所有复杂的解决方案中,它变得如此优雅! - meedstrom

3

在尝试使用mangini的方法将ArrayBuffer转换为String时,我遇到了一些处理大型数组时的问题。具体来说,调用String.fromCharCode.apply(null, new Uint16Array(buf));会抛出错误:

arguments array passed to Function.prototype.apply is too large

为了解决这个问题,我决定分块处理输入的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变量来改变它的大小。保持偶数非常重要。

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


你可以使用typedarray.subarray方法来获取指定位置和大小的块,这是我在JS中读取二进制格式头的方法。 - Nikos M.

3

3
那段代码受 GPLv3 许可。我认为 Mozilla 甚至将该代码与符合标准的文档混合使用是相当不专业的。 - user239558

2

对我来说,这个方法很有效。

  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
  }

2

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

1

我建议不要使用已弃用的API,如BlobBuilder

BlobBuilder早已被Blob对象所取代。请将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构造函数在2012年并不是很实用 ;) - gengkev

0

0

来自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);

0
使用展开语法代替循环:

arrbuf = new Uint8Array([104, 101, 108, 108, 111])
text = String.fromCharCode(...arrbuf)
console.log(text)

对于子字符串,可以使用arrbuf.slice()

对于非ASCII字符(UTF-8字节),它无法正常工作。 - crayze

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接