ArrayBuffer in TypeScript

ArrayBuffer in TypeScript

Questo articolo spiega ArrayBuffer in TypeScript.

Spiegheremo ArrayBuffer in TypeScript passo dopo passo, dai concetti di base alle tecniche pratiche.

YouTube Video

ArrayBuffer in TypeScript

ArrayBuffer è un oggetto integrato che rappresenta una “zona di memoria grezza” per dati binari. Rappresenta un buffer grezzo di lunghezza fissa, sopra il quale si sovrappongono TypedArray o DataView per leggere e scrivere.

Concetti di base e caratteristiche di ArrayBuffer

ArrayBuffer è una sequenza di byte a lunghezza fissa. Si specifica la dimensione in byte quando lo si crea e la sua lunghezza non può essere modificata successivamente.

1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
  • Questo codice crea un buffer vuoto di 16 byte. Puoi controllare la dimensione usando byteLength.

TypedArray e DataView — Viste per la manipolazione dei buffer

ArrayBuffer non ha la capacità di leggere o scrivere dati. Pertanto, le operazioni effettive vengono eseguite tramite TypedArray o DataView, specificando tipi come interi o numeri in virgola mobile ed endianità durante l’accesso ai dati.

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 è una vista di array byte per byte e può essere utilizzato come un array normale. Se crei più viste sullo stesso ArrayBuffer, esse condividono la stessa memoria per leggere e scrivere.

DataView: Lettura e scrittura su limiti arbitrari e endianness

DataView è utile per leggere e scrivere a grana fine per byte, e permette di specificare sia little che 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 consente di leggere e scrivere valori specificando offset nella memoria, rendendolo adatto per implementare protocolli che richiedono la gestione dell’ordine dei byte di rete.

Conversione tra stringhe e ArrayBuffer (TextEncoder / TextDecoder)

Per convertire testo in binario e viceversa, utilizzare TextEncoder e 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 restituisce un Uint8Array, quindi se hai bisogno di un ArrayBuffer dovresti fare riferimento alla sua proprietà .buffer. Con il metodo slice puoi estrarre i dati necessari specificando l’offset e la lunghezza.

Suddivisione e copia di ArrayBuffer

Il metodo slice restituisce un nuovo ArrayBuffer. ArrayBuffer non può essere ridimensionato; se è necessario cambiare misura, crea un nuovo buffer e copia i dati al suo interno.

 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
  • Poiché il metodo slice restituisce un nuovo ArrayBuffer, se dai priorità all’efficienza o alla condivisione delle referenze, puoi usare il metodo subarray di TypedArray per riferirti allo stesso buffer.

Differenza tra TypedArray.subarray e copia

subarray di TypedArray restituisce una nuova vista che fa riferimento allo stesso 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]
  • Le viste condivise possono evitare il costo della copia, ma poiché si riferiscono allo stesso buffer, occorre fare attenzione agli effetti collaterali.

Concatenazione dei buffer (combinazione di più ArrayBuffer)

Poiché ArrayBuffer ha lunghezza fissa, per combinare più buffer crea un nuovo ArrayBuffer e copia i dati.

 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]
  • Se concateni frequentemente grandi quantità di dati, calcolare anticipatamente la dimensione totale e allocare un nuovo buffer solo una volta, come in questo codice, è più efficiente.

Passaggio di ArrayBuffer a un Worker (Transferable)

Nel postMessage del browser, puoi trasferire un ArrayBuffer come oggetto "transferable". La proprietà può essere trasferita senza copia, evitando così il costo della duplicazione.

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
  • Specificando gli oggetti da trasferire in un array come secondo argomento di postMessage, puoi trasferire la proprietà, senza copia, di oggetti transferable come ArrayBuffer.
  • Dopo il trasferimento, il buffer diventa "detached" dal lato originale e non può più essere accessibile. L’uso di SharedArrayBuffer consente l’accesso simultaneo da più thread, ma comporta requisiti di sicurezza e restrizioni sull’ambiente.

Gestione in Node.js (interconversione con Buffer)

In Node.js, puoi convertire tra il tipo binario Buffer di Node.js e ArrayBuffer. Questo è utile quando si mira sia ai browser che a Node.js con 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) in Node.js di solito crea una copia, ma in alcuni casi è possibile condividere riferimenti, quindi fai attenzione agli offset.

Considerazioni sulle prestazioni e migliori pratiche

Per ottimizzare le prestazioni e gestire ArrayBuffer in modo efficiente, ci sono diversi punti importanti da tenere a mente. Di seguito elenchiamo e spieghiamo alcune buone pratiche.

  • Riduci il numero di copie Quando gestisci grandi binari, usa subarray (vista condivisa) o trasferibili per ridurre le copie.

  • Alloca grandi buffer in una sola volta L'allocazione ripetuta di piccoli buffer aumenta l'overhead. Se possibile, alloca un buffer grande in una sola volta e utilizza parti di esso secondo necessità.

  • Specifica esplicitamente l'endianness Quando gestisci valori multibyte usa DataView e specifica esplicitamente l'endianness. Big-endian è spesso lo standard per i protocolli di rete.

Esempi di casi d'uso comuni

ArrayBuffer è ampiamente utilizzato sia nei browser sia in Node.js.

  1. Parsing e costruzione di protocolli binari (elaborazione delle informazioni di header con DataView)
  2. Elaborazione di media come dati di immagini e audio (fetch(...).then(res => res.arrayBuffer())
  3. Memoria condivisa per WebAssembly (la memoria Wasm funziona su ArrayBuffer)
  4. Scaricamento dell'elaborazione onerosa ai Worker (passando ArrayBuffer come trasferibile)

Il seguente codice è un esempio di acquisizione e analisi di dati binari.

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}
  • Questo codice recupera dati binari da qualsiasi URL nel browser e mostra i primi byte. Il codice carica i dati recuperati tramite l'API fetch come ArrayBuffer e ispeziona i primi 16 byte con un Uint8Array.

Riepilogo

ArrayBuffer è una rappresentazione della memoria grezza, che permette efficienti operazioni binarie tramite TypedArray e DataView. Progettando per evitare copie non necessarie e specificando esplicitamente l'endianness, puoi ottenere un'elaborazione binaria sicura e ad alte prestazioni.

Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.

YouTube Video