Questa pagina è intesa come introduzione al lavoro con dati binari in JavaScript. Bun implementa una serie di tipi di dati e utility per lavorare con dati binari, la maggior parte dei quali sono standard Web. Le API specifiche di Bun verranno indicate come tali.
Di seguito è riportata una rapida "cheat sheet" che funge anche da indice. Clicca su un elemento nella colonna di sinistra per saltare a quella sezione.
| Classe | Descrizione |
|---|---|
TypedArray | Una famiglia di classi che forniscono un'interfaccia simile ad Array per interagire con dati binari. Include Uint8Array, Uint16Array, Int8Array e altro. |
Buffer | Una sottoclasse di Uint8Array che implementa un'ampia gamma di metodi di convenienza. A differenza degli altri elementi in questa tabella, questa è un'API Node.js (che Bun implementa). Non può essere usata nel browser. |
DataView | Una classe che fornisce un'API get/set per scrivere un certo numero di byte in un ArrayBuffer a un particolare offset di byte. Spesso usata per leggere o scrivere protocolli binari. |
Blob | Un blob di sola lettura di dati binari che rappresenta solitamente un file. Ha un type MIME, una size e metodi per convertire in ArrayBuffer, ReadableStream e stringa. |
File | Una sottoclasse di Blob che rappresenta un file. Ha un name e un timestamp lastModified. C'è un supporto sperimentale in Node.js v20. |
BunFile | Solo Bun. Una sottoclasse di Blob che rappresenta un file caricato lazy su disco. Creata con Bun.file(path). |
ArrayBuffer e viste
Fino al 2009, non esisteva un modo nativo nel linguaggio per memorizzare e manipolare dati binari in JavaScript. ECMAScript v5 ha introdotto una serie di nuovi meccanismi per questo. Il mattone fondamentale più importante è ArrayBuffer, una semplice struttura dati che rappresenta una sequenza di byte in memoria.
// questo buffer può memorizzare 8 byte
const buf = new ArrayBuffer(8);Nonostante il nome, non è un array e non supporta nessuno dei metodi e operatori di array che ci si potrebbe aspettare. Infatti, non c'è modo di leggere o scrivere direttamente valori da un ArrayBuffer. C'è ben poco che puoi fare con uno se non controllarne la dimensione e creare "slice" da esso.
const buf = new ArrayBuffer(8);
buf.byteLength; // => 8
const slice = buf.slice(0, 4); // restituisce un nuovo ArrayBuffer
slice.byteLength; // => 4Per fare qualcosa di interessante abbiamo bisogno di un costrutto noto come "vista". Una vista è una classe che avvolge un'istanza ArrayBuffer e ti permette di leggere e manipolare i dati sottostanti. Ci sono due tipi di viste: typed arrays e DataView.
DataView
La classe DataView è un'interfaccia di livello inferiore per leggere e manipolare i dati in un ArrayBuffer.
Di seguito creiamo un nuovo DataView e impostiamo il primo byte a 3.
const buf = new ArrayBuffer(4);
// [0b00000000, 0b00000000, 0b00000000, 0b00000000]
const dv = new DataView(buf);
dv.setUint8(0, 3); // scrivi il valore 3 all'offset di byte 0
dv.getUint8(0); // => 3
// [0b00000011, 0b00000000, 0b00000000, 0b00000000]Ora scriviamo un Uint16 all'offset di byte 1. Questo richiede due byte. Stiamo usando il valore 513, che è 2 * 256 + 1; in byte, è 00000010 00000001.
dv.setUint16(1, 513);
// [0b00000011, 0b00000010, 0b00000001, 0b00000000]
console.log(dv.getUint16(1)); // => 513Abbiamo ora assegnato un valore ai primi tre byte nel nostro ArrayBuffer sottostante. Anche se il secondo e terzo byte sono stati creati usando setUint16(), possiamo ancora leggere ciascuno dei suoi byte componenti usando getUint8().
console.log(dv.getUint8(1)); // => 2
console.log(dv.getUint8(2)); // => 1Tentare di scrivere un valore che richiede più spazio di quello disponibile nell'ArrayBuffer sottostante causerà un errore. Di seguito tentiamo di scrivere un Float64 (che richiede 8 byte) all'offset di byte 0, ma ci sono solo quattro byte totali nel buffer.
dv.setFloat64(0, 3.1415);
// ^ RangeError: Out of bounds accessI seguenti metodi sono disponibili su DataView:
TypedArray
I typed array sono una famiglia di classi che forniscono un'interfaccia simile ad Array per interagire con i dati in un ArrayBuffer. Mentre un DataView ti permette di scrivere numeri di dimensioni variabili a un particolare offset, un TypedArray interpreta i byte sottostanti come un array di numeri, ciascuno di dimensione fissa.
NOTE
È comune riferirsi a questa famiglia di classi collettivamente con la loro superclasse condivisa `TypedArray`. Questa classe è _interna_ a JavaScript; non puoi creare direttamente istanze di essa, e `TypedArray` non è definita nello scope globale. Pensala come un'`interface` o una classe astratta.const buffer = new ArrayBuffer(3);
const arr = new Uint8Array(buffer);
// i contenuti sono inizializzati a zero
console.log(arr); // Uint8Array(3) [0, 0, 0]
// assegna valori come un array
arr[0] = 0;
arr[1] = 10;
arr[2] = 255;
arr[3] = 255; // no-op, fuori dai limitiMentre un ArrayBuffer è una sequenza generica di byte, queste classi di typed array interpretano i byte come un array di numeri di una data dimensione in byte. La riga superiore contiene i byte grezzi, e le righe successive contengono come questi byte verranno interpretati quando visti usando diverse classi di typed array.
Le seguenti classi sono typed array, insieme a una descrizione di come interpretano i byte in un ArrayBuffer:
| Classe | Descrizione |
|---|---|
Uint8Array | Ogni uno (1) byte è interpretato come un intero senza segno a 8 bit. Intervallo da 0 a 255. |
Uint16Array | Ogni due (2) byte sono interpretati come un intero senza segno a 16 bit. Intervallo da 0 a 65535. |
Uint32Array | Ogni quattro (4) byte sono interpretati come un intero senza segno a 32 bit. Intervallo da 0 a 4294967295. |
Int8Array | Ogni uno (1) byte è interpretato come un intero con segno a 8 bit. Intervallo da -128 a 127. |
Int16Array | Ogni due (2) byte sono interpretati come un intero con segno a 16 bit. Intervallo da -32768 a 32767. |
Int32Array | Ogni quattro (4) byte sono interpretati come un intero con segno a 32 bit. Intervallo da -2147483648 a 2147483647. |
Float16Array | Ogni due (2) byte sono interpretati come un numero in virgola mobile a 16 bit. Intervallo da -6.104e5 a 6.55e4. |
Float32Array | Ogni quattro (4) byte sono interpretati come un numero in virgola mobile a 32 bit. Intervallo da -3.4e38 a 3.4e38. |
Float64Array | Ogni otto (8) byte sono interpretati come un numero in virgola mobile a 64 bit. Intervallo da -1.7e308 a 1.7e308. |
BigInt64Array | Ogni otto (8) byte sono interpretati come un BigInt con segno. Intervallo da -9223372036854775808 a 9223372036854775807 (anche se BigInt può rappresentare numeri più grandi). |
BigUint64Array | Ogni otto (8) byte sono interpretati come un BigInt senza segno. Intervallo da 0 a 18446744073709551615 (anche se BigInt può rappresentare numeri più grandi). |
Uint8ClampedArray | Come Uint8Array, ma "clampa" automaticamente all'intervallo 0-255 quando si assegna un valore a un elemento. |
La tabella sotto dimostra come i byte in un ArrayBuffer vengono interpretati quando visti usando diverse classi di typed array.
| Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 | |
|---|---|---|---|---|---|---|---|---|
ArrayBuffer | 00000000 | 00000001 | 00000010 | 00000011 | 00000100 | 00000101 | 00000110 | 00000111 |
Uint8Array | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Uint16Array | 256 (1 * 256 + 0) | 770 (3 * 256 + 2) | 1284 (5 * 256 + 4) | 1798 (7 * 256 + 6) | ||||
Uint32Array | 50462976 | 117835012 | ||||||
BigUint64Array | 506097522914230528n |
Per creare un typed array da un ArrayBuffer predefinito:
// crea typed array da ArrayBuffer
const buf = new ArrayBuffer(10);
const arr = new Uint8Array(buf);
arr[0] = 30;
arr[1] = 60;
// tutti gli elementi sono inizializzati a zero
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];Se provassimo a istanziare un Uint32Array da questo stesso ArrayBuffer, otterremmo un errore.
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
// ^ RangeError: ArrayBuffer length minus the byteOffset
// non è un multiplo della dimensione dell'elementoUn valore Uint32 richiede quattro byte (16 bit). Poiché l'ArrayBuffer è lungo 10 byte, non c'è modo di dividere pulitamente i suoi contenuti in blocchi da 4 byte.
Per risolvere questo problema, possiamo creare un typed array su una particolare "slice" di un ArrayBuffer. L'Uint16Array sottostante "vede" solo i primi 8 byte dell'ArrayBuffer sottostante. Per ottenere questo, specifichiamo un byteOffset di 0 e una length di 2, che indica il numero di numeri Uint32 che vogliamo che il nostro array contenga.
// crea typed array da slice di ArrayBuffer
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf, 0, 2);
/*
buf _ _ _ _ _ _ _ _ _ _ 10 byte
arr [_______,_______] 2 elementi da 4 byte
*/
arr.byteOffset; // 0
arr.length; // 2Non è necessario creare esplicitamente un'istanza ArrayBuffer; puoi invece specificare direttamente una lunghezza nel costruttore del typed array:
const arr2 = new Uint8Array(5);
// tutti gli elementi sono inizializzati a zero
// => Uint8Array(5) [0, 0, 0, 0, 0]I typed array possono anche essere istanziati direttamente da un array di numeri o da un altro typed array:
// da un array di numeri
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
arr1[0]; // => 0;
arr1[7]; // => 7;
// da un altro typed array
const arr2 = new Uint8Array(arr);In generale, i typed array forniscono gli stessi metodi dei normali array, con alcune eccezioni. Per esempio, push e pop non sono disponibili sui typed array, perché richiederebbero il ridimensionamento dell'ArrayBuffer sottostante.
const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
// supporta i metodi comuni degli array
arr.filter(n => n > 128); // Uint8Array(1) [255]
arr.map(n => n * 2); // Uint8Array(8) [0, 2, 4, 6, 8, 10, 12, 14]
arr.reduce((acc, n) => acc + n, 0); // 28
arr.forEach(n => console.log(n)); // 0 1 2 3 4 5 6 7
arr.every(n => n < 10); // true
arr.find(n => n > 5); // 6
arr.includes(5); // true
arr.indexOf(5); // 5Fai riferimento alla documentazione MDN per maggiori informazioni sulle proprietà e i metodi dei typed array.
Uint8Array
Vale la pena evidenziare specificamente Uint8Array, poiché rappresenta un classico "byte array"—una sequenza di interi senza segno a 8 bit tra 0 e 255. Questo è il typed array più comune che incontrerai in JavaScript.
In Bun, e un giorno in altri motori JavaScript, ha metodi disponibili per convertire tra byte array e rappresentazioni serializzate di quegli array come stringhe base64 o hex.
new Uint8Array([1, 2, 3, 4, 5]).toBase64(); // "AQIDBA=="
Uint8Array.fromBase64("AQIDBA=="); // Uint8Array(4) [1, 2, 3, 4, 5]
new Uint8Array([255, 254, 253, 252, 251]).toHex(); // "fffefdfcfb=="
Uint8Array.fromHex("fffefdfcfb"); // Uint8Array(5) [255, 254, 253, 252, 251]È il valore di ritorno di TextEncoder#encode e il tipo di input di TextDecoder#decode, due classi utility progettate per tradurre stringhe e vari encoding binari, in particolare "utf-8".
const encoder = new TextEncoder();
const bytes = encoder.encode("hello world");
// => Uint8Array(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
const decoder = new TextDecoder();
const text = decoder.decode(bytes);
// => hello worldBuffer
Bun implementa Buffer, un'API Node.js per lavorare con dati binari che precede l'introduzione dei typed array nelle specifiche JavaScript. Da allora è stato reimplementato come sottoclasse di Uint8Array. Fornisce un'ampia gamma di metodi, inclusi diversi metodi simili ad Array e DataView.
const buf = Buffer.from("hello world");
// => Buffer(11) [ 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100 ]
buf.length; // => 11
buf[0]; // => 104, ascii per 'h'
buf.writeUInt8(72, 0); // => ascii per 'H'
console.log(buf.toString());
// => Hello worldPer la documentazione completa, fai riferimento alla documentazione Node.js.
Blob
Blob è un'API Web comunemente usata per rappresentare file. Blob è stata inizialmente implementata nei browser (a differenza di ArrayBuffer che fa parte di JavaScript stesso), ma ora è supportata in Node e Bun.
Non è comune creare direttamente istanze Blob. Più spesso, riceverai istanze di Blob da una fonte esterna (come un elemento <input type="file"> nel browser) o da una libreria. Detto questo, è possibile creare un Blob da una o più "parti blob" di stringhe o binarie.
const blob = new Blob(["<html>Hello</html>"], {
type: "text/html",
});
blob.type; // => text/html
blob.size; // => 19Queste parti possono essere string, ArrayBuffer, TypedArray, DataView o altre istanze Blob. Le parti blob vengono concatenate nell'ordine in cui vengono fornite.
const blob = new Blob([
"<html>",
new Blob(["<body>"]),
new Uint8Array([104, 101, 108, 108, 111]), // "hello" in binario
"</body></html>",
]);I contenuti di un Blob possono essere letti in modo asincrono in vari formati.
await blob.text(); // => <html><body>hello</body></html>
await blob.bytes(); // => Uint8Array (copia i contenuti)
await blob.arrayBuffer(); // => ArrayBuffer (copia i contenuti)
await blob.stream(); // => ReadableStreamBunFile
BunFile è una sottoclasse di Blob usata per rappresentare un file caricato lazy su disco. Come File, aggiunge una proprietà name e lastModified. A differenza di File, non richiede che il file venga caricato in memoria.
const file = Bun.file("index.txt");
// => BunFileFile
File è una sottoclasse di Blob che aggiunge una proprietà name e lastModified. È comunemente usata nel browser per rappresentare file caricati tramite un elemento <input type="file">. Node.js e Bun implementano File.
// nel browser!
// <input type="file" id="file" />
const files = document.getElementById("file").files;
// => File[]const file = new File(["<html>Hello</html>"], "index.html", {
type: "text/html",
});Fai riferimento alla documentazione MDN per informazioni complete sulla documentazione.
Stream
Gli stream sono un'astrazione importante per lavorare con dati binari senza caricarli tutti in memoria contemporaneamente. Sono comunemente usati per leggere e scrivere file, inviare e ricevere richieste di rete ed elaborare grandi quantità di dati.
Bun implementa le API Web ReadableStream e WritableStream.
NOTE
Bun implementa anche il modulo `node:stream`, inclusi [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams) e [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). Per la documentazione completa, fai riferimento ai docs di Node.js.Per creare un semplice stream leggibile:
const stream = new ReadableStream({
start(controller) {
controller.enqueue("hello");
controller.enqueue("world");
controller.close();
},
});I contenuti di questo stream possono essere letti chunk per chunk con la sintassi for await.
for await (const chunk of stream) {
console.log(chunk);
}
// => "hello"
// => "world"Per una discussione più completa degli stream in Bun, vedi API > Streams.
Conversione
Convertire da un formato binario a un altro è un compito comune. Questa sezione è intesa come riferimento.
Da ArrayBuffer
Poiché ArrayBuffer memorizza i dati che stanno alla base di altre strutture binarie come TypedArray, gli snippet sotto non stanno convertendo da ArrayBuffer a un altro formato. Invece, stanno creando una nuova istanza usando i dati memorizzati nei dati sottostanti.
A TypedArray
new Uint8Array(buf);A DataView
new DataView(buf);A Buffer
// crea Buffer su intero ArrayBuffer
Buffer.from(buf);
// crea Buffer su una slice dell'ArrayBuffer
Buffer.from(buf, 0, 10);A string
Come UTF-8:
new TextDecoder().decode(buf);A number[]
Array.from(new Uint8Array(buf));A Blob
new Blob([buf], { type: "text/plain" });A ReadableStream
Lo snippet seguente crea un ReadableStream e accoda l'intero ArrayBuffer come un singolo chunk.
new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});Con chunking
Per streammare l'ArrayBuffer in chunk, usa una vista Uint8Array e accoda ogni chunk.
const view = new Uint8Array(buf);
const chunkSize = 1024;
new ReadableStream({
start(controller) {
for (let i = 0; i < view.length; i += chunkSize) {
controller.enqueue(view.slice(i, i + chunkSize));
}
controller.close();
},
});Da TypedArray
A ArrayBuffer
Questo recupera l'ArrayBuffer sottostante. Nota che un TypedArray può essere una vista di una slice del buffer sottostante, quindi le dimensioni possono differire.
arr.buffer;A DataView
Per creare un DataView sullo stesso intervallo di byte del TypedArray.
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);A Buffer
Buffer.from(arr);A string
Come UTF-8:
new TextDecoder().decode(arr);A number[]
Array.from(arr);A Blob
// solo se arr è una vista dell'intero TypedArray di backing
new Blob([arr.buffer], { type: "text/plain" });A ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(arr);
controller.close();
},
});Con chunking
Per streammare l'ArrayBuffer in chunk, dividi il TypedArray in chunk e accoda ciascuno individualmente.
new ReadableStream({
start(controller) {
for (let i = 0; i < arr.length; i += chunkSize) {
controller.enqueue(arr.slice(i, i + chunkSize));
}
controller.close();
},
});Da DataView
A ArrayBuffer
view.buffer;A TypedArray
Funziona solo se il byteLength del DataView è un multiplo del BYTES_PER_ELEMENT della sottoclasse TypedArray.
new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
new Uint16Array(view.buffer, view.byteOffset, view.byteLength / 2);
new Uint32Array(view.buffer, view.byteOffset, view.byteLength / 4);
// ecc...A Buffer
Buffer.from(view.buffer, view.byteOffset, view.byteLength);A string
Come UTF-8:
new TextDecoder().decode(view);A number[]
Array.from(view);A Blob
new Blob([view.buffer], { type: "text/plain" });A ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(view.buffer);
controller.close();
},
});Con chunking
Per streammare l'ArrayBuffer in chunk, dividi il DataView in chunk e accoda ciascuno individualmente.
new ReadableStream({
start(controller) {
for (let i = 0; i < view.byteLength; i += chunkSize) {
controller.enqueue(view.buffer.slice(i, i + chunkSize));
}
controller.close();
},
});Da Buffer
A ArrayBuffer
buf.buffer;A TypedArray
new Uint8Array(buf);A DataView
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);A string
Come UTF-8:
buf.toString();Come base64:
buf.toString("base64");Come hex:
buf.toString("hex");A number[]
Array.from(buf);A Blob
new Blob([buf], { type: "text/plain" });A ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});Con chunking
Per streammare l'ArrayBuffer in chunk, dividi il Buffer in chunk e accoda ciascuno individualmente.
new ReadableStream({
start(controller) {
for (let i = 0; i < buf.length; i += chunkSize) {
controller.enqueue(buf.slice(i, i + chunkSize));
}
controller.close();
},
});Da Blob
A ArrayBuffer
La classe Blob fornisce un metodo di convenienza per questo scopo.
await blob.arrayBuffer();A TypedArray
await blob.bytes();A DataView
new DataView(await blob.arrayBuffer());A Buffer
Buffer.from(await blob.arrayBuffer());A string
Come UTF-8:
await blob.text();A number[]
Array.from(await blob.bytes());A ReadableStream
blob.stream();Da ReadableStream
È comune usare Response come rappresentazione intermedia conveniente per rendere più facile convertire ReadableStream in altri formati.
stream; // ReadableStream
const buffer = new Response(stream).arrayBuffer();Tuttavia, questo approccio è verboso e aggiunge overhead che rallenta inutilmente le prestazioni complessive. Bun implementa un set di funzioni ottimizzate per convertire ReadableStream in vari formati binari.
A ArrayBuffer
// con Response
new Response(stream).arrayBuffer();
// con funzione Bun
Bun.readableStreamToArrayBuffer(stream);A Uint8Array
// con Response
new Response(stream).bytes();
// con funzione Bun
Bun.readableStreamToBytes(stream);A TypedArray
// con Response
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);
// con funzione Bun
new Int8Array(Bun.readableStreamToArrayBuffer(stream));A DataView
// con Response
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);
// con funzione Bun
new DataView(Bun.readableStreamToArrayBuffer(stream));A Buffer
// con Response
const buf = await new Response(stream).arrayBuffer();
Buffer.from(buf);
// con funzione Bun
Buffer.from(Bun.readableStreamToArrayBuffer(stream));A string
Come UTF-8:
// con Response
await new Response(stream).text();
// con funzione Bun
await Bun.readableStreamToText(stream);A number[]
// con Response
const arr = await new Response(stream).bytes();
Array.from(arr);
// con funzione Bun
Array.from(new Uint8Array(Bun.readableStreamToArrayBuffer(stream)));Bun fornisce un'utility per risolvere un ReadableStream in un array dei suoi chunk. Ogni chunk può essere una stringa, typed array o ArrayBuffer.
// con funzione Bun
Bun.readableStreamToArray(stream);A Blob
new Response(stream).blob();A ReadableStream
Per dividere un ReadableStream in due stream che possono essere consumati indipendentemente:
const [a, b] = stream.tee();