Esta página é uma introdução para trabalhar com dados binários em JavaScript. O Bun implementa vários tipos de dados e utilitários para trabalhar com dados binários, a maioria são padrões Web. Quaisquer APIs específicas do Bun serão anotadas como tal.
Abaixo está uma "cola" rápida que também serve como tabela de conteúdos. Clique em um item na coluna da esquerda para pular para aquela seção.
| Classe | Descrição |
|---|---|
TypedArray | Uma família de classes que fornecem uma interface tipo Array para interagir com dados binários. Inclui Uint8Array, Uint16Array, Int8Array e mais. |
Buffer | Uma subclasse de Uint8Array que implementa uma ampla gama de métodos de conveniência. Ao contrário dos outros elementos nesta tabela, esta é uma API Node.js (que o Bun implementa). Não pode ser usada no navegador. |
DataView | Uma classe que fornece uma API get/set para escrever um número de bytes em um ArrayBuffer em um offset de byte específico. Frequentemente usada para ler ou escrever protocolos binários. |
Blob | Um blob readonly de dados binários geralmente representando um arquivo. Tem um type MIME, um size e métodos para converter para ArrayBuffer, ReadableStream e string. |
File | Uma subclasse de Blob que representa um arquivo. Tem um timestamp name e lastModified. Há suporte experimental no Node.js v20. |
BunFile | Apenas Bun. Uma subclasse de Blob que representa um arquivo carregado preguiçosamente no disco. Criado com Bun.file(path). |
ArrayBuffer e views
Até 2009, não havia uma forma nativa na linguagem para armazenar e manipular dados binários em JavaScript. ECMAScript v5 introduziu uma gama de novos mecanismos para isso. O bloco de construção mais fundamental é ArrayBuffer, uma estrutura de dados simples que representa uma sequência de bytes na memória.
// este buffer pode armazenar 8 bytes
const buf = new ArrayBuffer(8);Apesar do nome, não é um array e não suporta nenhum dos métodos e operadores de array que se poderia esperar. Na verdade, não há como ler ou escrever valores diretamente de um ArrayBuffer. Há muito pouco que você pode fazer com ele exceto verificar seu tamanho e criar "slices" dele.
const buf = new ArrayBuffer(8);
buf.byteLength; // => 8
const slice = buf.slice(0, 4); // retorna novo ArrayBuffer
slice.byteLength; // => 4Para fazer algo interessante precisamos de uma construção conhecida como "view". Uma view é uma classe que envolve uma instância ArrayBuffer e permite ler e manipular os dados subjacentes. Existem dois tipos de views: typed arrays e DataView.
DataView
A classe DataView é uma interface de baixo nível para ler e manipular os dados em um ArrayBuffer.
Abaixo criamos uma nova DataView e definimos o primeiro byte como 3.
const buf = new ArrayBuffer(4);
// [0b00000000, 0b00000000, 0b00000000, 0b00000000]
const dv = new DataView(buf);
dv.setUint8(0, 3); // escreve valor 3 no byte offset 0
dv.getUint8(0); // => 3
// [0b00000011, 0b00000000, 0b00000000, 0b00000000]Agora vamos escrever um Uint16 no byte offset 1. Isso requer dois bytes. Estamos usando o valor 513, que é 2 * 256 + 1; em bytes, isso é 00000010 00000001.
dv.setUint16(1, 513);
// [0b00000011, 0b00000010, 0b00000001, 0b00000000]
console.log(dv.getUint16(1)); // => 513Agora atribuímos um valor aos primeiros três bytes no nosso ArrayBuffer subjacente. Mesmo que o segundo e terceiro bytes tenham sido criados usando setUint16(), ainda podemos ler cada um de seus bytes componentes usando getUint8().
console.log(dv.getUint8(1)); // => 2
console.log(dv.getUint8(2)); // => 1Tentar escrever um valor que requer mais espaço do que está disponível no ArrayBuffer subjacente causará um erro. Abaixo tentamos escrever um Float64 (que requer 8 bytes) no byte offset 0, mas há apenas quatro bytes totais no buffer.
dv.setFloat64(0, 3.1415);
// ^ RangeError: Out of bounds accessOs seguintes métodos estão disponíveis em DataView:
TypedArray
Typed arrays são uma família de classes que fornecem uma interface tipo Array para interagir com dados em um ArrayBuffer. Enquanto uma DataView permite escrever números de tamanhos variados em um offset específico, uma TypedArray interpreta os bytes subjacentes como um array de números, cada um de tamanho fixo.
NOTE
É comum referir-se a esta família de classes coletivamente pela sua superclasse compartilhada `TypedArray`. Esta classe é _interna_ ao JavaScript; você não pode criar instâncias diretamente dela, e `TypedArray` não está definida no escopo global. Pense nisso como uma `interface` ou uma classe abstrata.const buffer = new ArrayBuffer(3);
const arr = new Uint8Array(buffer);
// conteúdos são inicializados com zero
console.log(arr); // Uint8Array(3) [0, 0, 0]
// atribui valores como um array
arr[0] = 0;
arr[1] = 10;
arr[2] = 255;
arr[3] = 255; // no-op, fora dos limitesEnquanto um ArrayBuffer é uma sequência genérica de bytes, estas classes de typed array interpretam os bytes como um array de números de um determinado tamanho de byte. A linha superior contém os bytes brutos, e as linhas posteriores contêm como esses bytes serão interpretados quando visualizados usando diferentes classes de typed array.
As seguintes classes são typed arrays, junto com uma descrição de como interpretam os bytes em um ArrayBuffer:
| Classe | Descrição |
|---|---|
Uint8Array | Cada um (1) byte é interpretado como um inteiro sem sinal de 8 bits. Intervalo 0 a 255. |
Uint16Array | Cada dois (2) bytes são interpretados como um inteiro sem sinal de 16 bits. Intervalo 0 a 65535. |
Uint32Array | Cada quatro (4) bytes são interpretados como um inteiro sem sinal de 32 bits. Intervalo 0 a 4294967295. |
Int8Array | Cada um (1) byte é interpretado como um inteiro com sinal de 8 bits. Intervalo -128 a 127. |
Int16Array | Cada dois (2) bytes são interpretados como um inteiro com sinal de 16 bits. Intervalo -32768 a 32767. |
Int32Array | Cada quatro (4) bytes são interpretados como um inteiro com sinal de 32 bits. Intervalo -2147483648 a 2147483647. |
Float16Array | Cada dois (2) bytes são interpretados como um número de ponto flutuante de 16 bits. Intervalo -6.104e5 a 6.55e4. |
Float32Array | Cada quatro (4) bytes são interpretados como um número de ponto flutuante de 32 bits. Intervalo -3.4e38 a 3.4e38. |
Float64Array | Cada oito (8) bytes são interpretados como um número de ponto flutuante de 64 bits. Intervalo -1.7e308 a 1.7e308. |
BigInt64Array | Cada oito (8) bytes são interpretados como um BigInt com sinal. Intervalo -9223372036854775808 a 9223372036854775807 (embora BigInt seja capaz de representar números maiores). |
BigUint64Array | Cada oito (8) bytes são interpretados como um BigInt sem sinal. Intervalo 0 a 18446744073709551615 (embora BigInt seja capaz de representar números maiores). |
Uint8ClampedArray | Mesmo que Uint8Array, mas automaticamente "clamp" para o intervalo 0-255 ao atribuir um valor a um elemento. |
A tabela abaixo demonstra como os bytes em um ArrayBuffer são interpretados quando visualizados usando diferentes classes de 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 |
Para criar uma typed array a partir de um ArrayBuffer pré-definido:
// cria typed array a partir de ArrayBuffer
const buf = new ArrayBuffer(10);
const arr = new Uint8Array(buf);
arr[0] = 30;
arr[1] = 60;
// todos os elementos são inicializados com zero
console.log(arr); // => Uint8Array(10) [ 30, 60, 0, 0, 0, 0, 0, 0, 0, 0 ];Se tentássemos instanciar uma Uint32Array deste mesmo ArrayBuffer, obteríamos um erro.
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf);
// ^ RangeError: ArrayBuffer length minus the byteOffset
// is not a multiple of the element sizeUm valor Uint32 requer quatro bytes (16 bits). Como o ArrayBuffer tem 10 bytes de comprimento, não há como dividir limpa mente seu conteúdo em blocos de 4 bytes.
Para corrigir isso, podemos criar uma typed array sobre um "slice" específico de um ArrayBuffer. A Uint16Array abaixo apenas "visualiza" os primeiros 8 bytes do ArrayBuffer subjacente. Para conseguir isso, especificamos um byteOffset de 0 e um length de 2, que indica o número de números Uint32 que queremos que nosso array contenha.
// cria typed array a partir de slice de ArrayBuffer
const buf = new ArrayBuffer(10);
const arr = new Uint32Array(buf, 0, 2);
/*
buf _ _ _ _ _ _ _ _ _ _ 10 bytes
arr [_______,_______] 2 elementos de 4 bytes
*/
arr.byteOffset; // 0
arr.length; // 2Você não precisa criar explicitamente uma instância ArrayBuffer; você pode especificar diretamente um comprimento no construtor da typed array:
const arr2 = new Uint8Array(5);
// todos os elementos são inicializados com zero
// => Uint8Array(5) [0, 0, 0, 0, 0]Typed arrays também podem ser instanciadas diretamente de um array de números, ou outra typed array:
// de um array de números
const arr1 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
arr1[0]; // => 0;
arr1[7]; // => 7;
// de outra typed array
const arr2 = new Uint8Array(arr);De modo geral, typed arrays fornecem os mesmos métodos que arrays normais, com algumas exceções. Por exemplo, push e pop não estão disponíveis em typed arrays, porque exigiriam redimensionar o ArrayBuffer subjacente.
const arr = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
// suporta métodos comuns de 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); // 5Consulte a documentação MDN para mais informações sobre as propriedades e métodos de typed arrays.
Uint8Array
Vale a pena destacar especificamente Uint8Array, pois representa um "byte array" clássico—uma sequência de inteiros sem sinal de 8 bits entre 0 e 255. Esta é a typed array mais comum que você encontrará em JavaScript.
No Bun, e um dia em outros motores JavaScript, tem métodos disponíveis para converter entre byte arrays e representações serializadas desses arrays como strings base64 ou 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]É o valor de retorno de TextEncoder#encode, e o tipo de entrada de TextDecoder#decode, duas classes utilitárias projetadas para traduzir strings e várias codificações binárias, mais notavelmente "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
O Bun implementa Buffer, uma API Node.js para trabalhar com dados binários que antecede a introdução de typed arrays na especificação JavaScript. Desde então foi reimplementada como uma subclasse de Uint8Array. Fornece uma ampla gama de métodos, incluindo vários métodos tipo Array e tipo 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 para 'h'
buf.writeUInt8(72, 0); // => ascii para 'H'
console.log(buf.toString());
// => Hello worldPara documentação completa, consulte a documentação Node.js.
Blob
Blob é uma API Web comumente usada para representar arquivos. Blob foi inicialmente implementada em navegadores (ao contrário de ArrayBuffer que faz parte do próprio JavaScript), mas agora é suportada no Node e Bun.
Não é comum criar instâncias Blob diretamente. Mais frequentemente, você receberá instâncias de Blob de uma fonte externa (como um elemento <input type="file"> no navegador) ou biblioteca. Dito isso, é possível criar um Blob de uma ou mais "blob parts" de string ou binárias.
const blob = new Blob(["<html>Hello</html>"], {
type: "text/html",
});
blob.type; // => text/html
blob.size; // => 19Estas partes podem ser string, ArrayBuffer, TypedArray, DataView, ou outras instâncias Blob. As blob parts são concatenadas na ordem em que são fornecidas.
const blob = new Blob([
"<html>",
new Blob(["<body>"]),
new Uint8Array([104, 101, 108, 108, 111]), // "hello" em binário
"</body></html>",
]);O conteúdo de um Blob pode ser lido assincronamente em vários formatos.
await blob.text(); // => <html><body>hello</body></html>
await blob.bytes(); // => Uint8Array (copia o conteúdo)
await blob.arrayBuffer(); // => ArrayBuffer (copia o conteúdo)
await blob.stream(); // => ReadableStreamBunFile
BunFile é uma subclasse de Blob usada para representar um arquivo carregado preguiçosamente no disco. Como File, adiciona uma propriedade name e lastModified. Ao contrário de File, não requer que o arquivo seja carregado na memória.
const file = Bun.file("index.txt");
// => BunFileFile
File é uma subclasse de Blob que adiciona uma propriedade name e lastModified. É comumente usada no navegador para representar arquivos enviados via um elemento <input type="file">. Node.js e Bun implementam File.
// no navegador!
// <input type="file" id="file" />
const files = document.getElementById("file").files;
// => File[]const file = new File(["<html>Hello</html>"], "index.html", {
type: "text/html",
});Consulte a documentação MDN para informações completas de documentação.
Streams
Streams são uma abstração importante para trabalhar com dados binários sem carregá-los todos na memória de uma vez. São comumente usados para ler e escrever arquivos, enviar e receber requisições de rede, e processar grandes quantidades de dados.
O Bun implementa as APIs Web ReadableStream e WritableStream.
NOTE
O Bun também implementa o módulo `node:stream`, incluindo [`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). Para documentação completa, consulte a documentação Node.js.Para criar um stream legível simples:
const stream = new ReadableStream({
start(controller) {
controller.enqueue("hello");
controller.enqueue("world");
controller.close();
},
});O conteúdo deste stream pode ser lido chunk por chunk com a sintaxe for await.
for await (const chunk of stream) {
console.log(chunk);
}
// => "hello"
// => "world"Para uma discussão mais completa sobre streams no Bun, veja API > Streams.
Conversão
Converter de um formato binário para outro é uma tarefa comum. Esta seção é uma referência.
De ArrayBuffer
Como ArrayBuffer armazena os dados que fundamentam outras estruturas binárias como TypedArray, os snippets abaixo não estão convertendo de ArrayBuffer para outro formato. Em vez disso, estão criando uma nova instância usando os dados armazenados subjacentes.
Para TypedArray
new Uint8Array(buf);Para DataView
new DataView(buf);Para Buffer
// cria Buffer sobre todo o ArrayBuffer
Buffer.from(buf);
// cria Buffer sobre um slice do ArrayBuffer
Buffer.from(buf, 0, 10);Para string
Como UTF-8:
new TextDecoder().decode(buf);Para number[]
Array.from(new Uint8Array(buf));Para Blob
new Blob([buf], { type: "text/plain" });Para ReadableStream
O snippet abaixo cria um ReadableStream e enfileira todo o ArrayBuffer como um único chunk.
new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});Com chunking">
Para fazer stream do ArrayBuffer em chunks, use uma view Uint8Array e enfileire cada 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();
},
});De TypedArray
Para ArrayBuffer
Isso recupera o ArrayBuffer subjacente. Note que uma TypedArray pode ser uma view de um slice do buffer subjacente, então os tamanhos podem diferir.
arr.buffer;Para DataView
Para criar uma DataView sobre o mesmo intervalo de bytes que a TypedArray.
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);Para Buffer
Buffer.from(arr);Para string
Como UTF-8:
new TextDecoder().decode(arr);Para number[]
Array.from(arr);Para Blob
// apenas se arr for uma view de todo o seu TypedArray de backing
new Blob([arr.buffer], { type: "text/plain" });Para ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(arr);
controller.close();
},
});Com chunking">
Para fazer stream do ArrayBuffer em chunks, divida a TypedArray em chunks e enfileire cada um individualmente.
new ReadableStream({
start(controller) {
for (let i = 0; i < arr.length; i += chunkSize) {
controller.enqueue(arr.slice(i, i + chunkSize));
}
controller.close();
},
});De DataView
Para ArrayBuffer
view.buffer;Para TypedArray
Funciona apenas se o byteLength da DataView for um múltiplo do BYTES_PER_ELEMENT da subclasse 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);
// etc...Para Buffer
Buffer.from(view.buffer, view.byteOffset, view.byteLength);Para string
Como UTF-8:
new TextDecoder().decode(view);Para number[]
Array.from(view);Para Blob
new Blob([view.buffer], { type: "text/plain" });Para ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(view.buffer);
controller.close();
},
});Com chunking">
Para fazer stream do ArrayBuffer em chunks, divida a DataView em chunks e enfileire cada um individualmente.
new ReadableStream({
start(controller) {
for (let i = 0; i < view.byteLength; i += chunkSize) {
controller.enqueue(view.buffer.slice(i, i + chunkSize));
}
controller.close();
},
});De Buffer
Para ArrayBuffer
buf.buffer;Para TypedArray
new Uint8Array(buf);Para DataView
new DataView(buf.buffer, buf.byteOffset, buf.byteLength);Para string
Como UTF-8:
buf.toString();Como base64:
buf.toString("base64");Como hex:
buf.toString("hex");Para number[]
Array.from(buf);Para Blob
new Blob([buf], { type: "text/plain" });Para ReadableStream
new ReadableStream({
start(controller) {
controller.enqueue(buf);
controller.close();
},
});Com chunking">
Para fazer stream do ArrayBuffer em chunks, divida o Buffer em chunks e enfileire cada um individualmente.
new ReadableStream({
start(controller) {
for (let i = 0; i < buf.length; i += chunkSize) {
controller.enqueue(buf.slice(i, i + chunkSize));
}
controller.close();
},
});De Blob
Para ArrayBuffer
A classe Blob fornece um método de conveniência para este propósito.
await blob.arrayBuffer();Para TypedArray
await blob.bytes();Para DataView
new DataView(await blob.arrayBuffer());Para Buffer
Buffer.from(await blob.arrayBuffer());Para string
Como UTF-8:
await blob.text();Para number[]
Array.from(await blob.bytes());Para ReadableStream
blob.stream();De ReadableStream
É comum usar Response como uma representação intermediária conveniente para facilitar a conversão de ReadableStream para outros formatos.
stream; // ReadableStream
const buffer = new Response(stream).arrayBuffer();No entanto, esta abordagem é verbosa e adiciona sobrecarga que desacelera desnecessariamente o desempenho geral. O Bun implementa um conjunto de funções de conveniência otimizadas para converter ReadableStream para vários formatos binários.
Para ArrayBuffer
// com Response
new Response(stream).arrayBuffer();
// com função Bun
Bun.readableStreamToArrayBuffer(stream);Para Uint8Array
// com Response
new Response(stream).bytes();
// com função Bun
Bun.readableStreamToBytes(stream);Para TypedArray
// com Response
const buf = await new Response(stream).arrayBuffer();
new Int8Array(buf);
// com função Bun
new Int8Array(Bun.readableStreamToArrayBuffer(stream));Para DataView
// com Response
const buf = await new Response(stream).arrayBuffer();
new DataView(buf);
// com função Bun
new DataView(Bun.readableStreamToArrayBuffer(stream));Para Buffer
// com Response
const buf = await new Response(stream).arrayBuffer();
Buffer.from(buf);
// com função Bun
Buffer.from(Bun.readableStreamToArrayBuffer(stream));Para string
Como UTF-8:
// com Response
await new Response(stream).text();
// com função Bun
await Bun.readableStreamToText(stream);Para number[]
// com Response
const arr = await new Response(stream).bytes();
Array.from(arr);
// com função Bun
Array.from(new Uint8Array(Bun.readableStreamToArrayBuffer(stream)));O Bun fornece um utilitário para resolver um ReadableStream para um array de seus chunks. Cada chunk pode ser uma string, typed array, ou ArrayBuffer.
// com função Bun
Bun.readableStreamToArray(stream);Para Blob
new Response(stream).blob();Para ReadableStream
Para dividir um ReadableStream em dois streams que podem ser consumidos independentemente:
const [a, b] = stream.tee();