Skip to content

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.

ClasseDescrizione
TypedArrayUna famiglia di classi che forniscono un'interfaccia simile ad Array per interagire con dati binari. Include Uint8Array, Uint16Array, Int8Array e altro.
BufferUna 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.
DataViewUna 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.
BlobUn 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.
FileUna sottoclasse di Blob che rappresenta un file. Ha un name e un timestamp lastModified. C'è un supporto sperimentale in Node.js v20.
BunFileSolo 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.

ts
// 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.

ts
const buf = new ArrayBuffer(8);
buf.byteLength; // => 8

const slice = buf.slice(0, 4); // restituisce un nuovo ArrayBuffer
slice.byteLength; // => 4

Per 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.

ts
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.

ts
dv.setUint16(1, 513);
// [0b00000011, 0b00000010, 0b00000001, 0b00000000]

console.log(dv.getUint16(1)); // => 513

Abbiamo 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().

ts
console.log(dv.getUint8(1)); // => 2
console.log(dv.getUint8(2)); // => 1

Tentare 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.

ts
dv.setFloat64(0, 3.1415);
// ^ RangeError: Out of bounds access

I seguenti metodi sono disponibili su DataView:

GetterSetter
getBigInt64()setBigInt64()
getBigUint64()setBigUint64()
getFloat32()setFloat32()
getFloat64()setFloat64()
getInt16()setInt16()
getInt32()setInt32()
getInt8()setInt8()
getUint16()setUint16()
getUint32()setUint32()
getUint8()setUint8()

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.
ts
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 limiti

Mentre 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:

ClasseDescrizione
Uint8ArrayOgni uno (1) byte è interpretato come un intero senza segno a 8 bit. Intervallo da 0 a 255.
Uint16ArrayOgni due (2) byte sono interpretati come un intero senza segno a 16 bit. Intervallo da 0 a 65535.
Uint32ArrayOgni quattro (4) byte sono interpretati come un intero senza segno a 32 bit. Intervallo da 0 a 4294967295.
Int8ArrayOgni uno (1) byte è interpretato come un intero con segno a 8 bit. Intervallo da -128 a 127.
Int16ArrayOgni due (2) byte sono interpretati come un intero con segno a 16 bit. Intervallo da -32768 a 32767.
Int32ArrayOgni quattro (4) byte sono interpretati come un intero con segno a 32 bit. Intervallo da -2147483648 a 2147483647.
Float16ArrayOgni due (2) byte sono interpretati come un numero in virgola mobile a 16 bit. Intervallo da -6.104e5 a 6.55e4.
Float32ArrayOgni quattro (4) byte sono interpretati come un numero in virgola mobile a 32 bit. Intervallo da -3.4e38 a 3.4e38.
Float64ArrayOgni otto (8) byte sono interpretati come un numero in virgola mobile a 64 bit. Intervallo da -1.7e308 a 1.7e308.
BigInt64ArrayOgni otto (8) byte sono interpretati come un BigInt con segno. Intervallo da -9223372036854775808 a 9223372036854775807 (anche se BigInt può rappresentare numeri più grandi).
BigUint64ArrayOgni otto (8) byte sono interpretati come un BigInt senza segno. Intervallo da 0 a 18446744073709551615 (anche se BigInt può rappresentare numeri più grandi).
Uint8ClampedArrayCome 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 0Byte 1Byte 2Byte 3Byte 4Byte 5Byte 6Byte 7
ArrayBuffer0000000000000001000000100000001100000100000001010000011000000111
Uint8Array01234567
Uint16Array256 (1 * 256 + 0)770 (3 * 256 + 2)1284 (5 * 256 + 4)1798 (7 * 256 + 6)
Uint32Array50462976117835012
BigUint64Array506097522914230528n

Per creare un typed array da un ArrayBuffer predefinito:

ts
// 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.

ts
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
//          ^  RangeError: ArrayBuffer length minus the byteOffset
//             non è un multiplo della dimensione dell'elemento

Un 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.

ts
// 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; // 2

Non è necessario creare esplicitamente un'istanza ArrayBuffer; puoi invece specificare direttamente una lunghezza nel costruttore del typed array:

ts
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:

ts
// 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.

ts
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); // 5

Fai 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.

ts
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".

ts
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 world

Buffer

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.

ts
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 world

Per 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.

ts
const blob = new Blob(["<html>Hello</html>"], {
  type: "text/html",
});

blob.type; // => text/html
blob.size; // => 19

Queste parti possono essere string, ArrayBuffer, TypedArray, DataView o altre istanze Blob. Le parti blob vengono concatenate nell'ordine in cui vengono fornite.

ts
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.

ts
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(); // => ReadableStream

BunFile

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.

ts
const file = Bun.file("index.txt");
// => BunFile

File

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.

ts
// nel browser!
// <input type="file" id="file" />

const files = document.getElementById("file").files;
// => File[]
ts
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:

ts
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.

ts
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

ts
new Uint8Array(buf);

A DataView

ts
new DataView(buf);

A Buffer

ts
// 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:

ts
new TextDecoder().decode(buf);

A number[]

ts
Array.from(new Uint8Array(buf));

A Blob

ts
new Blob([buf], { type: "text/plain" });

A ReadableStream

Lo snippet seguente crea un ReadableStream e accoda l'intero ArrayBuffer come un singolo chunk.

ts
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.

ts
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.

ts
arr.buffer;

A DataView

Per creare un DataView sullo stesso intervallo di byte del TypedArray.

ts
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);

A Buffer

ts
Buffer.from(arr);

A string

Come UTF-8:

ts
new TextDecoder().decode(arr);

A number[]

ts
Array.from(arr);

A Blob

ts
// solo se arr è una vista dell'intero TypedArray di backing
new Blob([arr.buffer], { type: "text/plain" });

A ReadableStream

ts
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.

ts
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

ts
view.buffer;

A TypedArray

Funziona solo se il byteLength del DataView è un multiplo del BYTES_PER_ELEMENT della sottoclasse TypedArray.

ts
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

ts
Buffer.from(view.buffer, view.byteOffset, view.byteLength);

A string

Come UTF-8:

ts
new TextDecoder().decode(view);

A number[]

ts
Array.from(view);

A Blob

ts
new Blob([view.buffer], { type: "text/plain" });

A ReadableStream

ts
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.

ts
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

ts
buf.buffer;

A TypedArray

ts
new Uint8Array(buf);

A DataView

ts
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);

A string

Come UTF-8:

ts
buf.toString();

Come base64:

ts
buf.toString("base64");

Come hex:

ts
buf.toString("hex");

A number[]

ts
Array.from(buf);

A Blob

ts
new Blob([buf], { type: "text/plain" });

A ReadableStream

ts
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.

ts
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.

ts
await blob.arrayBuffer();

A TypedArray

ts
await blob.bytes();

A DataView

ts
new DataView(await blob.arrayBuffer());

A Buffer

ts
Buffer.from(await blob.arrayBuffer());

A string

Come UTF-8:

ts
await blob.text();

A number[]

ts
Array.from(await blob.bytes());

A ReadableStream

ts
blob.stream();

Da ReadableStream

È comune usare Response come rappresentazione intermedia conveniente per rendere più facile convertire ReadableStream in altri formati.

ts
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

ts
// con Response
new Response(stream).arrayBuffer();

// con funzione Bun
Bun.readableStreamToArrayBuffer(stream);

A Uint8Array

ts
// con Response
new Response(stream).bytes();

// con funzione Bun
Bun.readableStreamToBytes(stream);

A TypedArray

ts
// con Response
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);

// con funzione Bun
new Int8Array(Bun.readableStreamToArrayBuffer(stream));

A DataView

ts
// con Response
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);

// con funzione Bun
new DataView(Bun.readableStreamToArrayBuffer(stream));

A Buffer

ts
// 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:

ts
// con Response
await new Response(stream).text();

// con funzione Bun
await Bun.readableStreamToText(stream);

A number[]

ts
// 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.

ts
// con funzione Bun
Bun.readableStreamToArray(stream);

A Blob

ts
new Response(stream).blob();

A ReadableStream

Per dividere un ReadableStream in due stream che possono essere consumati indipendentemente:

ts
const [a, b] = stream.tee();

Bun a cura di www.bunjs.com.cn