Como converter ArrayBuffer em uma string

Renato Mangini

Os ArrayBuffers são usados para transportar dados brutos, e várias novas APIs dependem deles, incluindo WebSockets, Web Intents 2](https://www.html5rocks.com/en/tutorials/file/xhr2/) e WebWorkers. No entanto, como eles chegaram recentemente ao mundo do JavaScript, às vezes são mal interpretados ou usados indevidamente.

Semanticamente, um ArrayBuffer é simplesmente uma matriz de bytes visualizada por uma máscara específica. Essa máscara, uma instância de ArrayBufferView, define como os bytes são alinhados para corresponder à estrutura esperada do conteúdo. Por exemplo, se você souber que os bytes em um ArrayBuffer representam uma matriz de números inteiros não assinados de 16 bits, basta agrupar o ArrayBuffer em uma visualização Uint16Array e manipular os elementos usando a sintaxe de colchetes como se o Uint16Array fosse uma matriz de números inteiros:

// 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]

Uma pergunta prática comum sobre ArrayBuffer é como converter um String em um ArrayBuffer e vice-versa. Como um ArrayBuffer é, na verdade, uma matriz de bytes, essa conversão exige que as duas extremidades concordem sobre como representar os caracteres na string como bytes. Você provavelmente já viu esse "contrato" antes: ele é a codificação de caracteres da string (e os "termos do contrato" usuais são, por exemplo, Unicode UTF-16 e iso8859-1). Assim, supondo que você e a outra parte concordaram na codificação UTF-16, o código de conversão pode ser algo como:

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

Observe o uso de Uint16Array. Esta é uma visualização de ArrayBuffer que alinha bytes de ArrayBuffers como elementos de 16 bits. Ele não processa a codificação de caracteres em si, que é processada como Unicode por String.fromCharCode e str.charCodeAt.

Uma pergunta sobre isso no StackOverflow tem uma resposta com muitos votos com uma solução um tanto complicada para a conversão: crie um FileReader para atuar como um conversor e alimente um Blob contendo a string. Embora esse método funcione, ele tem baixa legibilidade e provavelmente é lento. Como suspeitas infundadas causaram muitos erros na história da humanidade, vamos adotar uma abordagem mais científica. Testei os dois métodos com jsperf, e o resultado confirma minha suspeita. Confira a demonstração aqui.

No Chrome 20, o código de manipulação ArrayBuffer direto é quase 27 vezes mais rápido do que o método FileReader/Blob.