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