如何将 ArrayBuffer 与字符串进行转换

雷纳托·曼吉尼

ArrayBuffer 用于传输原始数据,并依赖于一些新的 API,包括 WebSocketsWeb intent 2](https://www.html5rocks.com/en/tutorials/file/xhr2/) 和 WebWorkers。但是,由于它们最近出现在 JavaScript 中,因此有时会被误解或滥用。

在语义上,ArrayBuffer 只是一个通过特定掩码查看的字节数组。此掩码是 ArrayBufferView 的一个实例,用于定义如何对齐字节以匹配预期的内容结构。例如,如果您知道 ArrayBuffer 中的字节表示一个 16 位无符号整数数组,则只需将 ArrayBuffer 封装在 Uint16Array 视图中,就可以使用括号语法处理其元素,就像 Uint16Array 是一个整数数组一样:

// suppose buf contains the bytes [0x02, 0x01, 0x03, 0x07]
// notice the multibyte values respect the hardware endianess, which is little-endian in x86
var bufView = new Uint16Array(buf);
if (bufView[0]===258) {   // 258 === 0x0102
    console.log("ok");
}
bufView[0] = 255;    // buf now contains the bytes [0xFF, 0x00, 0x03, 0x07]
bufView[0] = 0xff05; // buf now contains the bytes [0x05, 0xFF, 0x03, 0x07]
bufView[1] = 0x0210; // buf now contains the bytes [0x05, 0xFF, 0x10, 0x02]

关于 ArrayBuffer 的一个常见实际问题是,如何在 StringArrayBuffer 之间进行转换。由于 ArrayBuffer 实际上是字节数组,因此这种转换要求两端就如何将 String 中的字符表示为字节达成一致。您以前可能见过此“协议”:它是字符串的字符编码(常见的“协议条款”例如 Unicode UTF-16 和 iso8859-1)。因此,假设您和另一方已就 UTF-16 编码达成一致,转换代码可能会如下所示:

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

请注意 Uint16Array 的用法。这是一个 ArrayBuffer 视图,用于将 ArrayBuffer 的字节对齐为 16 位元素。它不处理字符编码本身,而是由 String.fromCharCodestr.charCodeAt 作为 Unicode 处理的。

StackOverflow 一个热门的 问题 有一个高得票的答案,其中包含比较复杂的转化解决方案: 创建一个FileReader作为转换器,并向其中馈送包含该字符串的Blob。虽然这种方法行之有效,但可读性较差,我怀疑速度会比较慢。由于毫无根据的质疑在人类历史上造成了许多错误,因此我们在这里采取更科学的方法。我已对这两种方法执行了 jsperf 测试,结果证实了我的怀疑,您可以点击此处查看演示

在 Chrome 20 中,在本文中使用直接 ArrayBuffer 操作代码几乎比使用 FileReader/Blob 方法快 27 倍。