ArrayBuffer no TypeScript

ArrayBuffer no TypeScript

Este artigo explica o ArrayBuffer no TypeScript.

Vamos explicar o ArrayBuffer no TypeScript passo a passo, cobrindo desde os conceitos básicos até técnicas práticas.

YouTube Video

ArrayBuffer no TypeScript

ArrayBuffer é um objeto embutido que representa uma “área de memória bruta” para dados binários. Ele representa um buffer bruto de comprimento fixo, sobre o qual TypedArray ou DataView são aplicados para leitura e escrita.

Conceitos Básicos e Características do ArrayBuffer

ArrayBuffer é uma sequência de bytes de comprimento fixo. Você especifica o tamanho em bytes ao criá-lo, e seu comprimento não pode ser alterado depois.

1// Create a new ArrayBuffer of 16 bytes.
2const buf = new ArrayBuffer(16);
3console.log(buf.byteLength); // 16
  • Este código cria um buffer vazio de 16 bytes. Você pode verificar o tamanho usando byteLength.

TypedArray e DataView — Visões para manipulação de buffers

ArrayBuffer não tem a capacidade de ler ou escrever dados. Portanto, as operações reais são realizadas por meio de TypedArray ou DataView, especificando tipos como inteiros ou números de ponto flutuante e a endianness ao acessar os dados.

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 é uma visão de array byte a byte, e pode ser acessado como um array normal. Se você colocar várias visualizações sobre o mesmo ArrayBuffer, elas compartilham a mesma memória para leitura e escrita.

DataView: Leitura e Escrita em Limites Arbitrários e Endianness

DataView é útil para leitura e escrita detalhada byte a byte e permite especificar little ou 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 permite ler e escrever valores especificando offsets na memória, o que o torna adequado para implementar protocolos que exigem manipulação de ordem de bytes de rede.

Conversão entre Strings e ArrayBuffer (TextEncoder / TextDecoder)

Para converter texto em binário e vice-versa, utilize 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 retorna um Uint8Array, portanto, se precisar de um ArrayBuffer, deve consultar sua propriedade .buffer. Com o método slice, você pode extrair os dados necessários especificando o offset e o comprimento.

Fatiando e Copiando ArrayBuffer

O método slice retorna um novo ArrayBuffer. ArrayBuffer não pode ser redimensionado; se for necessário redimensionar, crie um novo buffer e copie os dados para ele.

 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
  • Como o método slice retorna um novo ArrayBuffer, se você prioriza eficiência ou compartilhamento de referências, pode utilizar o método subarray do TypedArray para referenciar o mesmo buffer.

Diferença entre TypedArray.subarray e Cópia

O subarray do TypedArray retorna uma nova visão que se refere ao mesmo 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]
  • Visões compartilhadas podem evitar o custo de cópias, mas como referenciam o mesmo buffer, é necessário ter cuidado com efeitos colaterais.

Concatenando Buffers (Combinando Múltiplos ArrayBuffers)

Como o ArrayBuffer tem comprimento fixo, para combinar vários buffers, crie um novo ArrayBuffer e copie os dados.

 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 você frequentemente concatena grandes quantidades de dados, pré-calcular o tamanho total e alocar um novo buffer apenas uma vez, como neste código, é mais eficiente.

Passando ArrayBuffer para um Worker (Transferível)

No postMessage do navegador, você pode transferir um ArrayBuffer como um objeto "transferível". A propriedade pode ser transferida sem cópia, o que evita o custo da duplicação.

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
  • Ao especificar os objetos a serem transferidos em um array como segundo argumento do postMessage, é possível transferir a propriedade, sem cópia, de objetos transferíveis, como ArrayBuffer.
  • Após a transferência, o buffer torna-se "desanexado" no lado original e não pode mais ser acessado. O uso de SharedArrayBuffer permite acesso simultâneo de múltiplas threads, mas seu uso exige requisitos de segurança e restrições de ambiente.

Tratamento no Node.js (Interconversão com Buffer)

No Node.js, você pode converter entre o tipo binário do Node.js, Buffer, e ArrayBuffer. Isso é útil ao desenvolver para navegadores e Node.js com 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) no Node.js normalmente cria uma cópia, mas há casos em que o compartilhamento de referência é possível, então fique atento aos offsets.

Considerações de Desempenho e Melhores Práticas

Para otimizar o desempenho e manipular ArrayBuffer de forma eficiente, há vários pontos importantes a considerar. A seguir, listamos e explicamos melhores práticas práticas.

  • Reduza o Número de Cópias Ao manipular binários grandes, use subarray (visualização compartilhada) ou objetos transferíveis para reduzir cópias.

  • Aloque Grandes Buffers de Uma Só Vez Alocar pequenos buffers repetidamente aumenta a sobrecarga. Se possível, aloque um buffer grande de uma vez e utilize partes dele conforme necessário.

  • Especifique o Endianness Explicitamente Ao manipular valores multibyte, use DataView e especifique explicitamente o endianness. Big-endian é geralmente o padrão para protocolos de rede.

Exemplos Comuns de Uso

ArrayBuffer é amplamente utilizado tanto em navegadores quanto no Node.js.

  1. Análise e construção de protocolos binários (processando informações de cabeçalho com DataView)
  2. Processamento de mídia como dados de imagens e áudio (fetch(...).then(res => res.arrayBuffer()))
  3. Memória compartilhada para WebAssembly (a memória Wasm opera baseada em ArrayBuffer)
  4. Descarregando processamento pesado para Workers (passando ArrayBuffer como transferível)

O código a seguir é um exemplo de obtenção e análise de dados binários.

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}
  • Este código busca dados binários de qualquer URL no navegador e exibe os primeiros bytes. O código carrega dados buscados via a API fetch como um ArrayBuffer e inspeciona os primeiros 16 bytes com um Uint8Array.

Resumo

ArrayBuffer é uma representação de memória bruta, possibilitando operações binárias eficientes via TypedArray e DataView. Ao projetar para evitar cópias desnecessárias e especificar explicitamente o endianness, é possível obter um processamento binário seguro e de alto desempenho.

Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.

YouTube Video