Como converter ArrayBuffer em uma string

Renato Mangini

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

Semanticamente, um ArrayBuffer é simplesmente uma matriz de bytes visualizados 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ê sabe que os bytes em um ArrayBuffer representam uma matriz de números inteiros não assinados de 16 bits, basta unir o ArrayBuffer em uma visualização de Uint16Array e manipular seus elementos usando a sintaxe de colchetes como se 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 "acordo" antes: é a codificação de caracteres da string (e os "termos do acordo" usuais são, por exemplo, Unicode UTF-16 e iso8859-1). Desse modo, supondo que você e a outra parte concordem com a 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. Essa é uma visualização de ArrayBuffer que alinha bytes dos ArrayBuffers como elementos de 16 bits. Ele não processa a codificação de caracteres, que é processada como Unicode por String.fromCharCode e str.charCodeAt.

Uma pergunta sobre isso (link em inglês) conhecida no StackOverflow tem uma resposta muito votada com uma solução um pouco complicada para a conversão: criar um FileReader para atuar como um conversor e alimentar um Blob contendo a String. Embora esse método funcione, ele tem baixa legibilidade, e sinto que ele seja lento. Como suspeitas infundadas geraram muitos erros na história da humanidade, vamos adotar uma abordagem mais científica aqui. Eu jsperf'ed os dois métodos e o resultado confirma minha suspeita, e você confira a demonstração aqui.

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