ArrayBuffer en TypeScript

ArrayBuffer en TypeScript

Cet article explique ArrayBuffer en TypeScript.

Nous expliquerons ArrayBuffer en TypeScript étape par étape, en couvrant les bases jusqu’aux techniques pratiques.

YouTube Video

ArrayBuffer en TypeScript

ArrayBuffer est un objet intégré qui représente une « zone mémoire brute » pour les données binaires. Il représente un tampon brut de longueur fixe, sur lequel TypedArray ou DataView sont superposés pour la lecture et l’écriture.

Principes de base et caractéristiques d’ArrayBuffer

ArrayBuffer est une séquence d’octets de longueur fixe. Vous spécifiez la taille en octets lors de sa création, et sa longueur ne peut pas être modifiée par la suite.

1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
  • Ce code crée un tampon vide de 16 octets. Vous pouvez vérifier la taille à l’aide de byteLength.

TypedArray et DataView — Vues pour manipuler des tampons

ArrayBuffer n’a pas la capacité de lire ou d’écrire des données. Par conséquent, les opérations réelles sont effectuées via TypedArray ou DataView, en spécifiant des types comme des entiers ou des nombres à virgule flottante et l’endianness lors de l’accès aux données.

1// Create a buffer and a Uint8Array view over it. Then write bytes and read them.
2const buffer = new ArrayBuffer(8);
3const u8 = new Uint8Array(buffer);
4
5u8[0] = 0x41; // 'A'
6u8[1] = 0x42; // 'B'
7console.log(u8); // Uint8Array(8) [ 65, 66, 0, 0, 0, 0, 0, 0 ]
  • Uint8Array est une vue de tableau octet par octet, qui peut être utilisée comme un tableau classique. Si vous placez plusieurs vues sur le même ArrayBuffer, elles partagent la même mémoire pour la lecture et l’écriture.

DataView : Lecture et écriture à des frontières et des endianness arbitraires

DataView est utile pour la lecture et l’écriture fine au niveau de l’octet, et permet de spécifier le mode little-endian ou big-endian.

 1// Using DataView to write/read multi-byte values with endianness control.
 2const buf2 = new ArrayBuffer(8);
 3const view = new DataView(buf2);
 4
 5// Write a 32-bit integer (little-endian)
 6view.setInt32(0, 0x12345678, true);
 7
 8// Read it back as little-endian and big-endian
 9const little = view.getInt32(0, true);
10const big = view.getInt32(0, false);
11console.log(little.toString(16)); // "12345678"
12console.log(big.toString(16));    // "78563412"
  • DataView vous permet de lire et d’écrire des valeurs en spécifiant des décalages en mémoire, ce qui le rend adapté pour implémenter des protocoles nécessitant la gestion de l’ordre des octets réseau.

Conversion entre chaînes de caractères et ArrayBuffer (TextEncoder / TextDecoder)

Pour convertir du texte en binaire et inversement, utilisez TextEncoder et TextDecoder.

 1// Convert string -> ArrayBuffer and back using TextEncoder/TextDecoder.
 2const encoder = new TextEncoder();
 3const decoder = new TextDecoder();
 4
 5// Unicode escape sequences
 6const str = "\u3053\u3093\u306B\u3061\u306F";
 7const encoded = encoder.encode(str); // Uint8Array
 8console.log(encoded); // Uint8Array([...])
 9
10// If you need an ArrayBuffer specifically:
11const ab = encoded.buffer.slice(encoded.byteOffset, encoded.byteOffset + encoded.byteLength);
12console.log(ab.byteLength); // bytes length of encoded text
13
14// Decode back
15const decoded = decoder.decode(encoded);
16console.log(decoded);
  • TextEncoder.encode retourne un Uint8Array, donc si vous avez besoin d’un ArrayBuffer, vous devez utiliser sa propriété .buffer. Avec la méthode slice, vous pouvez extraire les données requises en spécifiant le décalage et la longueur.

Découper et copier un ArrayBuffer

La méthode slice retourne un nouvel ArrayBuffer. ArrayBuffer ne peut pas être redimensionné ; si un redimensionnement est nécessaire, créez un nouveau tampon et copiez-y les données.

 1// Slice an ArrayBuffer and copy to a new sized buffer.
 2const original = new Uint8Array([1,2,3,4,5]).buffer;
 3const part = original.slice(1, 4); // bytes 1..3
 4console.log(new Uint8Array(part)); // Uint8Array [ 2, 3, 4 ]
 5
 6// Resize: create a new buffer and copy existing content
 7const larger = new ArrayBuffer(10);
 8const target = new Uint8Array(larger);
 9target.set(new Uint8Array(original), 0);
10console.log(target); // first bytes filled with original data
  • Comme la méthode slice retourne un nouvel ArrayBuffer, si vous privilégiez l’efficacité ou le partage des références, vous pouvez utiliser la méthode subarray de TypedArray pour référencer le même tampon.

Différence entre TypedArray.subarray et la copie

La méthode subarray de TypedArray retourne une nouvelle vue pointant vers le même ArrayBuffer.

1// subarray shares the same underlying buffer; modifying one affects the other.
2const arr = new Uint8Array([10,20,30,40]);
3const viewSub = arr.subarray(1,3); // shares memory
4viewSub[0] = 99;
5console.log(arr); // Uint8Array [10, 99, 30, 40]
  • Les vues partagées peuvent éviter le coût de la copie, mais comme elles se réfèrent au même tampon, il faut faire attention aux effets de bord.

Concaténation des tampons (combiner plusieurs ArrayBuffer)

ArrayBuffer étant de longueur fixe, pour combiner plusieurs tampons il faut créer un nouveau ArrayBuffer et copier les données.

 1// Concatenate multiple ArrayBuffers
 2function concatBuffers(buffers: ArrayBuffer[]): ArrayBuffer {
 3  const total = buffers.reduce((sum, b) => sum + b.byteLength, 0);
 4  const result = new Uint8Array(total);
 5  let offset = 0;
 6  for (const b of buffers) {
 7    const u8 = new Uint8Array(b);
 8    result.set(u8, offset);
 9    offset += u8.length;
10  }
11  return result.buffer;
12}
13
14const a = new Uint8Array([1,2]).buffer;
15const b = new Uint8Array([3,4,5]).buffer;
16const c = concatBuffers([a, b]);
17console.log(new Uint8Array(c)); // [1,2,3,4,5]
  • Si vous concaténez fréquemment de grandes quantités de données, il est plus efficace de pré-calculer la taille totale et d’allouer un nouveau tampon une seule fois, comme dans ce code.

Passer un ArrayBuffer à un Worker (transférable)

Dans le postMessage du navigateur, vous pouvez transférer un ArrayBuffer en tant qu’objet « transferable ». La propriété peut être transférée sans copie, ce qui évite le coût de la duplication.

1// Example: posting an ArrayBuffer to a Worker as a transferable object (browser)
2const worker = new Worker('worker.js');
3const bufferToSend = new Uint8Array([1,2,3,4]).buffer;
4
5// Transfer ownership to the worker (main thread no longer owns it)
6worker.postMessage(bufferToSend, [bufferToSend]);
7
8// After transfer, bufferToSend.byteLength === 0 in many browsers (detached)
9console.log(bufferToSend.byteLength); // may be 0
  • En spécifiant les objets à transférer dans un tableau en tant que second argument de postMessage, vous pouvez transférer la propriété, sans copier, d’objets transférables comme ArrayBuffer.
  • Après le transfert, le tampon devient « détaché » sur le côté d’origine et ne peut plus être utilisé. Utiliser SharedArrayBuffer permet un accès simultané depuis plusieurs threads, mais son utilisation implique des exigences de sécurité et des restrictions d’environnement.

Gestion dans Node.js (conversion avec Buffer)

Dans Node.js, vous pouvez convertir entre le type binaire Node.js Buffer et ArrayBuffer. Cela est utile lorsque l’on cible à la fois les navigateurs et Node.js avec TypeScript.

 1// Convert ArrayBuffer <-> Node.js Buffer
 2// In Node.js environment:
 3const ab = new Uint8Array([10,20,30]).buffer;
 4const nodeBuffer = Buffer.from(ab); // ArrayBuffer -> Buffer
 5console.log(nodeBuffer); // <Buffer 0a 14 1e>
 6
 7const backToAb = nodeBuffer.buffer.slice(
 8    nodeBuffer.byteOffset,
 9    nodeBuffer.byteOffset + nodeBuffer.byteLength
10);
11console.log(new Uint8Array(backToAb)); // Uint8Array [10,20,30]
  • Buffer.from(arrayBuffer) dans Node.js crée généralement une copie, mais il existe des cas où le partage de référence est possible, donc faites attention aux décalages.

Considérations de performance et bonnes pratiques

Pour optimiser les performances et gérer efficacement les ArrayBuffer, il y a plusieurs points importants à garder à l’esprit. Ci-dessous, nous énumérons et expliquons des bonnes pratiques concrètes.

  • Réduisez le nombre de copies Lors du traitement de gros binaires, utilisez subarray (vue partagée) ou des objets transférables pour réduire les copies.

  • Allouez les grands tampons d’un seul coup Allouer de petits tampons de manière répétée augmente la surcharge. Si possible, allouez un grand tampon en une seule fois et utilisez-en des portions selon les besoins.

  • Spécifiez explicitement l’ordre des octets (endianness) Lors du traitement de valeurs multioctets, utilisez DataView et spécifiez explicitement l’endianness. Le big-endian est souvent la norme pour les protocoles réseau.

Exemples d’utilisations courantes

ArrayBuffer est largement utilisé aussi bien dans les navigateurs que dans Node.js.

  1. Analyse et création de protocoles binaires (traitement des en-têtes avec DataView)
  2. Traitement de médias comme des images et des sons (fetch(...).then(res => res.arrayBuffer()))
  3. Mémoire partagée pour WebAssembly (la mémoire Wasm est basée sur ArrayBuffer)
  4. Décharger des traitements lourds vers des Workers (passage d’ArrayBuffer en tant que transférable)

Le code suivant est un exemple d’acquisition et d’analyse de données binaires.

1// Example: fetch binary data in browser and inspect first bytes
2async function fetchAndInspect(url: string) {
3  const resp = await fetch(url);
4  const ab = await resp.arrayBuffer();
5  const u8 = new Uint8Array(ab, 0, Math.min(16, ab.byteLength));
6  console.log('first bytes:', u8);
7}
  • Ce code récupère des données binaires depuis n’importe quelle URL dans le navigateur et affiche les premiers octets. Ce code charge des données récupérées via l’API fetch en tant qu’ArrayBuffer et inspecte les 16 premiers octets avec un Uint8Array.

Résumé

ArrayBuffer est une représentation mémoire brute, permettant des opérations binaires efficaces via TypedArray et DataView. En adoptant une conception qui évite les copies inutiles et en spécifiant explicitement l’endianness, vous pouvez réaliser un traitement binaire sûr et performant.

Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.

YouTube Video