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

Renato Mangini

ArrayBuffer 用于传输原始数据,并且有几个新 API 依赖于它们,包括 WebSocketsWeb Intents 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 的一个常见实际问题是如何将 String 转换为 ArrayBuffer,反之亦然。由于 ArrayBuffer 实际上是字节数组,因此此转换要求两端就如何将字符串中的字符表示为字节达成一致。您可能之前见过这种“协议”:它是字符串的字符编码(例如,常见的“协议条款”包括 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 倍。