Comment convertir un objet ArrayBuffer en valeur String

Renato Mangini

Les ArrayBuffers sont utilisés pour transporter des données brutes, et plusieurs nouvelles API s'appuient sur eux, y compris les WebSockets, les Web Intents 2](https://www.html5rocks.com/en/tutorials/file/xhr2/) et les WebWorkers. Cependant, comme ils sont récemment apparus dans le monde JavaScript, ils sont parfois mal interprétés ou mal utilisés.

Sémantiquement, un ArrayBuffer est simplement un tableau d'octets affiché à l'aide d'un masque spécifique. Ce masque, une instance de ArrayBufferView, définit la façon dont les octets sont alignés pour correspondre à la structure attendue du contenu. Par exemple, si vous savez que les octets d'un ArrayBuffer représentent un tableau d'entiers non signés 16 bits, il vous suffit d'encapsuler l'ArrayBuffer dans une vue Uint16Array et vous pouvez manipuler ses éléments à l'aide de la syntaxe des crochets comme si Uint16Array était un tableau d'entiers:

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

Une question pratique courante concernant ArrayBuffer est de savoir comment convertir un String en ArrayBuffer et inversement. Étant donné qu'un ArrayBuffer est en fait un tableau d'octets, cette conversion nécessite que les deux extrémités s'accordent sur la façon de représenter les caractères de la chaîne en tant qu'octets. Vous avez probablement déjà vu cet "accord" : il s'agit de l'encodage de caractères de la chaîne (et les "conditions de l'accord" habituelles sont, par exemple, Unicode UTF-16 et iso8859-1). Ainsi, supposons que vous et l'autre partie vous soyez mis d'accord sur l'encodage UTF-16. Le code de conversion pourrait être le suivant:

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

Notez l'utilisation de Uint16Array. Il s'agit d'une vue ArrayBuffer qui aligne les octets des ArrayBuffers en tant qu'éléments de 16 bits. Il ne gère pas l'encodage des caractères lui-même, qui est géré en tant qu'Unicode par String.fromCharCode et str.charCodeAt.

Une question populaire sur StackOverflow propose une réponse très appréciée avec une solution quelque peu complexe à la conversion : créez un FileReader pour qu'il serve de convertisseur et alimentez-le avec un Blob contenant la chaîne. Bien que cette méthode fonctionne, elle est peu lisible et je pense qu'elle est lente. Étant donné que des soupçons infondés ont conduit à de nombreuses erreurs dans l'histoire de l'humanité, adoptons une approche plus scientifique. J'ai utilisé jsperf pour les deux méthodes, et le résultat confirme mes soupçons. Découvrez la démonstration ici.

Dans Chrome 20, l'utilisation du code de manipulation directe ArrayBuffer de cet article est presque 27 fois plus rapide que l'utilisation de la méthode FileReader/Blob.