Typed Arrays e ArrayBuffer
LidoNesta aula, você aprenderá a trabalhar com dados binários em JavaScript usando ArrayBuffer e Typed Arrays. Exploraremos o que são, por que são importantes, como criar e manipular esses objetos, e veremos casos de uso práticos como processamento de imagens e arquivos.
Bem-vindo à aula 87 do nosso curso de JavaScript! Hoje vamos mergulhar em um tópico essencial para quem deseja trabalhar com dados binários de forma eficiente: Typed Arrays e ArrayBuffer. Em JavaScript, a manipulação de dados binários é muitas vezes negligenciada, mas é fundamental para aplicações que lidam com áudio, vídeo, imagens, protocolos de rede e até mesmo jogos. Compreender esses conceitos permitirá que você escreva código mais performático e capaz de interagir com APIs modernas, como Fetch, WebSockets e Canvas.
Nesta aula, vamos começar entendendo o que são dados binários e por que eles são importantes. Em seguida, exploraremos os casos de uso que justificam o uso de Typed Arrays. Por fim, faremos uma visão geral detalhada das principais classes e métodos, com exemplos práticos que você pode testar no seu navegador ou no Node.js.
Dados binários
Dados binários são sequências de bytes que representam informações em formato bruto, sem interpretação semântica. No computador, tudo é armazenado como bits (0 e 1), e os bytes (8 bits) são a unidade básica de armazenamento. Quando falamos de dados binários em JavaScript, nos referimos a sequências de números inteiros de 8 a 64 bits que podem representar qualquer coisa: caracteres, números, pixels, amostras de áudio, etc.
No JavaScript tradicional, não havia uma forma nativa de lidar com dados binários de forma eficiente. Strings eram usadas para muitos propósitos, mas elas são codificadas em UTF-16 e não são adequadas para representar bytes brutos, especialmente quando precisamos de alto desempenho. Para preencher essa lacuna, o ECMAScript 6 (ES6) introduziu os Typed Arrays e o ArrayBuffer, que são partes da especificação WebGL e foram adotados pela linguagem.
Um ArrayBuffer é um objeto que representa um bloco de memória de tamanho fixo, em bytes, mas não fornece acesso direto a esses bytes. Para ler ou escrever nesse buffer, precisamos de uma “visão” tipada, que é exatamente o que os Typed Arrays oferecem. Eles são objetos semelhantes a arrays que permitem interpretar os bytes do buffer como uma sequência de números de um tipo específico (int8, uint8, int16, float32, etc.).
Vamos ver um exemplo simples de como criar um ArrayBuffer e usar um Typed Array para manipular seus dados:
// Cria um buffer de 8 bytes
const buffer = new ArrayBuffer(8);
// Cria uma visão como array de inteiros de 8 bits sem sinal (uint8)
const uint8View = new Uint8Array(buffer);
// Preenche com valores
uint8View[0] = 42;
uint8View[1] = 255;
// Lê os valores
console.log(uint8View[0]); // 42
console.log(uint8View[1]); // 255
// Podemos também usar uma visão de 16 bits para interpretar os mesmos bytes
const uint16View = new Uint16Array(buffer);
console.log(uint16View[0]); // 65542? Depende da endianness
Observe que o mesmo buffer pode ser interpretado de formas diferentes, dependendo do tipo de visão. Isso é poderoso, mas também exige cuidado com a endianness (ordem dos bytes) ao ler dados de fontes externas.
Casos de uso
Os Typed Arrays são usados em uma ampla variedade de aplicações. Aqui estão alguns dos casos de uso mais comuns:
- Processamento de imagens: Ao manipular pixels de uma imagem, você pode usar um Uint8Array para representar os canais RGBA. Isso é feito em bibliotecas de manipulação de imagem e em aplicações de edição de fotos.
- Áudio e vídeo: APIs de áudio e vídeo, como Web Audio API e Media Source Extensions, utilizam buffers de dados binários para representar amostras de áudio e frames de vídeo.
- Protocolos de rede: Ao enviar dados pela rede, como em WebSockets ou WebRTC, é comum usar ArrayBuffers para transmitir dados binários de forma eficiente, sem a sobrecarga de strings.
- Leitura de arquivos: A API FileReader permite ler arquivos como ArrayBuffer, permitindo processar arquivos binários, como PDFs, ZIPs ou imagens, diretamente no navegador.
- WebGL e gráficos 3D: Os buffers de vértices e índices em WebGL são frequentemente armazenados em Typed Arrays para serem enviados à GPU.
- Criptografia e compressão: Algoritmos de hash, criptografia e compressão operam sobre bytes, e Typed Arrays são a estrutura ideal para tais operações.
Além desses, qualquer aplicação que precise de alto desempenho e controle fino sobre a representação binária de dados se beneficia dos Typed Arrays. Eles são muito mais rápidos que arrays comuns quando se trabalha com grandes volumes de dados, pois são armazenados em memória contígua e têm tipos fixos.
Visão geral
Vamos agora explorar os principais componentes: ArrayBuffer, DataView e os diferentes Typed Arrays.
ArrayBuffer
ArrayBuffer é o objeto básico que representa um bloco de memória fixo. Você pode criar um com um tamanho específico em bytes:
const buffer = new ArrayBuffer(16); // 16 bytes
console.log(buffer.byteLength); // 16
O buffer não pode ser redimensionado após a criação. Para acessar os dados, você precisa de uma visão. Existem duas formas de criar visões: Typed Arrays e DataView.
Typed Arrays
Typed Arrays são objetos que fornecem acesso a uma região do buffer como se fossem arrays de um tipo numérico específico. Aqui estão os principais construtores:
Int8Array– inteiros com sinal de 8 bits (valores de -128 a 127)Uint8Array– inteiros sem sinal de 8 bits (0 a 255)Uint8ClampedArray– como Uint8Array, mas valores fora do intervalo são fixados (clamped) em 0 ou 255Int16Array– inteiros com sinal de 16 bitsUint16Array– inteiros sem sinal de 16 bitsInt32Array– inteiros com sinal de 32 bitsUint32Array– inteiros sem sinal de 32 bitsFloat32Array– números de ponto flutuante de 32 bitsFloat64Array– números de ponto flutuante de 64 bits (double)
Cada Typed Array tem um elemento de tamanho fixo em bytes (por exemplo, Uint8Array tem 1 byte por elemento, Int16Array tem 2 bytes, Float32Array tem 4 bytes, etc.). Ao criar um Typed Array, você pode especificar um buffer e opcionalmente um deslocamento (offset) e comprimento (length).
const buffer = new ArrayBuffer(16);
const int32View = new Int32Array(buffer);
console.log(int32View.length); // 4 (16 bytes / 4 bytes por elemento)
// Criando uma visão a partir de um offset
const offset = 4; // começa no byte 4
const uint8View = new Uint8Array(buffer, offset, 4); // apenas 4 bytes a partir do offset
console.log(uint8View.length); // 4
Os Typed Arrays suportam os mesmos métodos que os arrays comuns, como map, filter, reduce, forEach, etc. No entanto, eles têm algumas diferenças: não podem ser redimensionados e o comprimento é fixo. Além disso, ao atribuir um valor fora do intervalo permitido, o valor é truncado ou clamped (dependendo do tipo).
DataView
DataView é uma alternativa mais flexível aos Typed Arrays, pois permite ler e escrever diferentes tipos de dados em qualquer posição do buffer, controlando a endianness. Isso é útil quando você precisa lidar com dados binários de formatos variados, como em arquivos ou protocolos de rede.
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
// Escreve um inteiro de 32 bits no byte 0 (little-endian)
view.setInt32(0, 123456, true); // true = little-endian
// Lê um inteiro de 32 bits
console.log(view.getInt32(0, true)); // 123456
// Escreve um float de 32 bits no byte 4
view.setFloat32(4, 3.14, true);
console.log(view.getFloat32(4, true)); // 3.140000104904175 (aproximado)
DataView é mais lento que Typed Arrays, mas oferece mais controle. Escolha a abordagem conforme a necessidade.
Conversões entre Typed Arrays e arrays comuns
Às vezes, você pode precisar converter um Typed Array em um array comum (ou vice-versa). Para converter para array comum, você pode usar Array.from() ou o operador spread:
const uint8 = new Uint8Array([1, 2, 3]);
const normalArray = Array.from(uint8);
// ou
const normalArray2 = [...uint8];
console.log(normalArray); // [1, 2, 3]
Para criar um Typed Array a partir de um array comum, basta passar o array ao construtor:
const normalArray = [1, 2, 3];
const uint8 = new Uint8Array(normalArray);
console.log(uint8); // Uint8Array [ 1, 2, 3 ]
Também é possível converter um ArrayBuffer em uma string base64 ou vice-versa, o que é comum ao enviar dados via JSON ou armazenar em localStorage.
Boas práticas
- Use Typed Arrays quando precisar de alto desempenho e controle sobre dados binários.
- Prefira Uint8Array para dados brutos, pois é o mais comum e eficiente.
- Tenha cuidado com endianness ao ler dados de fontes externas; use DataView se não tiver certeza.
- Evite criar muitos buffers grandes desnecessariamente; reutilize buffers quando possível.
- Verifique o suporte do navegador para APIs que usam ArrayBuffer (todas as modernas suportam).
Referências
- MDN: ArrayBuffer
- MDN: TypedArray
- MDN: DataView
- MDN: FileReader.readAsArrayBuffer
- MDN: WebSocket
- MDN: Web Audio API
- MDN: WebGL API
Exercícios
-
Crie um ArrayBuffer de 16 bytes e use um Uint8Array para preencher os primeiros 5 bytes com os valores 1, 2, 3, 4, 5. Depois, leia e imprima esses valores no console.
✓ Resposta:const buffer = new ArrayBuffer(16); const uint8 = new Uint8Array(buffer); uint8[0] = 1; uint8[1] = 2; uint8[2] = 3; uint8[3] = 4; uint8[4] = 5; console.log(uint8[0], uint8[1], uint8[2], uint8[3], uint8[4]); // 1 2 3 4 5 -
Explique a diferença entre Uint8Array e Uint8ClampedArray, e dê um exemplo onde Uint8ClampedArray seria mais adequado.
✓ Resposta: Uint8Array trunca valores fora do intervalo (0-255) para o valor módulo 256 (por exemplo, 256 vira 0, -1 vira 255). Uint8ClampedArray fixa (clamp) os valores: valores menores que 0 viram 0, e maiores que 255 viram 255. Uint8ClampedArray é mais adequado para manipulação de pixels, onde valores de canal devem ser limitados a 0-255 sem wraparound. Por exemplo, ao ajustar brilho de uma imagem, se um pixel exceder 255, ele deve ser fixado em 255, não voltar para 0. -
Crie um DataView a partir de um ArrayBuffer de 8 bytes e escreva um inteiro de 32 bits com valor 0x01020304 no início do buffer em big-endian. Depois, leia os bytes individualmente usando um Uint8Array e imprima a sequência.
✓ Resposta:const buffer = new ArrayBuffer(8); const view = new DataView(buffer); view.setInt32(0, 0x01020304, false); // false = big-endian const uint8 = new Uint8Array(buffer); console.log(uint8[0], uint8[1], uint8[2], uint8[3]); // 1 2 3 4 -
Como você converteria um ArrayBuffer contendo texto UTF-8 em uma string? Escreva uma função que recebe um ArrayBuffer e retorna a string correspondente.
✓ Resposta:function arrayBufferToString(buffer) { const uint8 = new Uint8Array(buffer); return new TextDecoder().decode(uint8); } // Exemplo de uso: // const buffer = new TextEncoder().encode('Olá, mundo!').buffer; // console.log(arrayBufferToString(buffer)); // Olá, mundo! -
Qual é a principal vantagem de usar Typed Arrays em vez de arrays comuns para manipular dados binários? Cite pelo menos duas vantagens.
✓ Resposta: 1. Desempenho: Typed Arrays são armazenados em memória contígua e têm tipos fixos, o que permite ao motor JavaScript otimizar o acesso e a manipulação, resultando em operações muito mais rápidas para grandes volumes de dados. 2. Controle sobre representação binária: Permitem interpretar bytes como inteiros, floats, etc., com tamanhos específicos, essencial para interoperar com APIs que exigem formatos binários precisos (como WebGL, Web Audio, etc.).
Observações finais
Nesta aula, você aprendeu os fundamentos dos Typed Arrays e do ArrayBuffer, que são ferramentas poderosas para programação de baixo nível em JavaScript. Dominar esses conceitos abrirá portas para áreas como processamento de mídia, jogos e comunicação em tempo real. Lembre-se de praticar bastante, explorando como combinar Typed Arrays com outras APIs do navegador, como Fetch e Canvas. Na próxima aula, vamos aprofundar em tópicos avançados, como a manipulação de arquivos e streams. Até lá!