Um Buffer é a resposta do Node para um problema que o JavaScript originalmente não tinha solução: como manter e manipular dados binários brutos - conteúdo de arquivos, pacotes de rede, bytes de imagem - em uma linguagem cuja única tipo numérico era um float de 64 bits inadequado para trabalho em nível de byte?
Noções Básicas de Buffers mostra a API do dia a dia para alocar e fatiar esses dados, e Codificações de Texto cobre o caso específico de converter bytes de e para strings. Esta página fica por baixo de ambas: o que um Buffer realmente é na memória, como ele se relaciona com a família padrão TypedArray, e por que "dados binários" e "texto" são duas preocupações separadas que só se encontram em uma etapa explícita de conversão.
Um Buffer é uma visualização de comprimento fixo sobre bytes brutos armazenados fora do heap com coleta de lixo do V8, exposto através de uma API que é um superconjunto do Uint8Array padrão.
Por que é Importante: Dados binários (sockets de rede, arquivos, criptografia) não se encaixam em uma linguagem construída em torno de valores dinâmicos e com coleta de lixo - o Buffer fornece ao Node um armazenamento previsível e de baixo overhead para exatamente esses dados.
Conceitos Chave:memória bruta, ArrayBuffer, TypedArray, visualização de bytes, codificação, pooling de buffer.
Quando Usar: Ler/escrever arquivos ou sockets em nível de byte, implementar um protocolo binário, gerar hash ou criptografar dados, ou converter entre texto e bytes em um limite do sistema.
Limitações / Compromissos: Buffers são de tamanho fixo após a alocação, vivem fora do heap que o coletor de lixo do V8 otimiza, e o caminho de alocação mais rápido (allocUnsafe) pode expor conteúdo de memória antigo se você não for cuidadoso.
Tópicos Relacionados: TypedArrays e ArrayBuffer, codificação de texto (UTF-8), streams, design de protocolo binário.
Antes do Buffer existir, o JavaScript não tinha uma boa maneira de representar "me dê exatamente estes 512 bytes e me permita ler ou escrever qualquer um deles diretamente". Números eram floats, strings eram sequências UTF-16, e nenhum deles se mapeia claramente para um fluxo de bytes de um socket TCP ou um arquivo em disco.
O Node introduziu o Buffer em suas primeiras versões para preencher essa lacuna: um array de comprimento fixo de inteiros, cada um restrito a um único byte (0-255), apoiado por memória alocada fora do heap normal do JavaScript. Esse detalhe "fora do heap" importa mais do que parece inicialmente - significa que grandes cargas de dados binários não pressionam o coletor de lixo do V8 como um array equivalente de números JavaScript fariam, e significa que a memória tem um layout bruto, semelhante a C, que pode ser passado diretamente para chamadas de sistema.
Uma analogia útil: pense em um Buffer como uma fileira numerada de armários de armazenamento, cada um contendo exatamente um byte, com um número total fixo decidido no momento em que a fileira é construída. Você pode ler ou sobrescrever qualquer armário pelo seu número, você pode entregar uma fatia de armários consecutivos para outra coisa sem copiar seus conteúdos, mas você não pode adicionar um armário à fileira ou remover um - o comprimento da fileira é fixo por toda a sua vida.
import { Buffer } from 'node:buffer';const buf = Buffer.alloc(4); // 4 bytes preenchidos com zero, fora do heap do V8buf[0] = 0xff; // escreve um byte diretamenteconsole.log(buf); // <Buffer ff 00 00 00>
Quando o JavaScript mais tarde padronizou seus próprios tipos de dados binários - ArrayBuffer e a família TypedArray (Uint8Array, Int32Array, e assim por diante) - o Node adaptou o Buffer para ficar em cima deles em vez de manter uma implementação totalmente separada.
Hoje, um Buffer é uma subclasse de Uint8Array, que por sua vez é uma visualização sobre um ArrayBuffer de nível inferior. Essa camada é o fato mecânico chave em que toda esta seção se baseia: o ArrayBuffer é o bloco real de memória bruta, e um TypedArray (incluindo Buffer) é uma lente que interpreta alguma ou toda essa memória como uma sequência de valores de tamanho igual - no caso do Buffer, sempre bytes únicos.
Como o Buffer é um Uint8Array por baixo dos panos, todos os métodos padrão TypedArray funcionam nele, e um Buffer pode compartilhar o mesmo ArrayBuffer subjacente com um Uint8Array criado em outro lugar - mutar um através de sua visualização pode ser visível através do outro, já que são janelas para a mesma memória em vez de cópias independentes. O Buffer do Node adiciona conveniência por cima: conversão de string com conhecimento de codificação, auxiliares de leitura/escrita de inteiros em offsets de byte arbitrários (readUInt32BE, writeInt16LE, e similares), e construtores ajustados para os casos de uso próprios do Node.
Codificação é a segunda mecânica que vale a pena separar claramente do modelo de memória: um Buffer nunca "é" UTF-8 ou qualquer outra codificação - são apenas bytes. A codificação é uma regra aplicada no momento da conversão, dizendo ao Node como interpretar esses bytes como texto (ou vice-versa) quando você explicitamente pede.
const buf = Buffer.from('café', 'utf8'); // 5 bytes - é leva 2 bytes em UTF-8console.log(buf.length); // 5, não 4console.log(buf.toString('utf8')); // 'café' - só correto com codificação correspondente
Essa distinção explica uma surpresa comum: buf.length conta bytes, não caracteres, e ler uma string codificada com múltiplos bytes com o argumento de codificação errado produz texto silenciosamente incorreto em vez de um erro, porque o Buffer não tem como saber qual codificação você pretendia.
A estratégia de alocação é a terceira mecânica, e é um compromisso direto de segurança de memória. Buffer.alloc(n) preenche com zeros sua memória antes de retorná-la, garantindo que não haja dados antigos vazando. Buffer.allocUnsafe(n) pula esse preenchimento com zero e em vez disso recicla memória de um pool interno pré-alocado para velocidade - o que significa que um Buffer inseguro recém-alocado pode conter temporariamente os bytes que estavam anteriormente naquele slot do pool, até que você os sobrescreva. Segurança de Buffer cobre exatamente quando esse compromisso é (e não é) aceitável.
A relação entre Buffer, TypedArray e as APIs binárias padrão da Web (TextEncoder, TextDecoder, Blob) convergiu significativamente à medida que o Node adotou mais da biblioteca padrão do navegador ao lado de sua própria biblioteca específica do Node - é por isso que o código moderno do Node mistura cada vez mais ambos livremente em vez de tratar o Buffer como um conceito exclusivo do Node.
Abordagem
Força
Fraqueza
Melhor Encaixe
Buffer
Métodos de conveniência nativos do Node (readUInt32BE, toString com conhecimento de codificação); sem dependência extra
Superfície de API específica do Node; historicamente alguns "footguns" (alocação insegura)
E/S de arquivos, sockets, código de protocolo binário exclusivo do Node
Uint8Array / ArrayBuffer simples
Padrão da Web, portátil para navegadores e outros runtimes JS inalterado
Menos métodos de conveniência integrados para manipulação em nível de byte
Código destinado a ser executado tanto no Node quanto no navegador
TextEncoder / TextDecoder
Padrão da Web, tratamento explícito e previsível de UTF-8 com modos de erro
Apenas texto - não é um contêiner de dados binários geral
Conversão estritamente entre texto UTF-8 e bytes
Em escala, a propriedade de memória fora do heap se torna uma preocupação operacional em vez de apenas uma nota de desempenho: um grande número de Buffers de longa duração reduz a pressão sobre o coletor de lixo do V8, mas eles ainda consomem memória do processo que as ferramentas de monitoramento precisam contabilizar separadamente das métricas típicas do heap. É parte do motivo pelo qual carregar um arquivo grande inteiro em um único Buffer gigante geralmente é a chamada errada - Noções Básicas de Streams cobre o processamento dos mesmos dados em blocos limitados em vez de mantê-los todos na memória de uma vez.
Código sensível à segurança tem sua própria ponta afiada aqui: como allocUnsafe recicla memória do pool, o código que aloca um buffer inseguro e o preenche apenas parcialmente antes de enviá-lo para algum lugar (um socket, um corpo de resposta) pode vazar fragmentos de dados anteriores não relacionados. Segurança de Buffer cobre isso e preocupações relacionadas, como comparação segura em tempo para dados de bytes criptográficos. Protocolos Binários se baseia nas mecânicas de leitura/escrita em offset descritas acima para analisar e construir formatos de rede reais.
"Um Buffer armazena texto com uma codificação anexada a ele." Um Buffer armazena apenas bytes - a codificação é uma regra de conversão aplicada quando você explicitamente converte de ou para uma string, não uma propriedade que os bytes carregam consigo.
"buf.length me dá a contagem de caracteres do texto que ele contém." Ele conta os bytes - para qualquer codificação que use mais de um byte por caractere (os caracteres não ASCII do UTF-8, por exemplo), esses números divergem.
"Buffer e Uint8Array são APIs não relacionadas e concorrentes." Buffer é uma subclasse de Uint8Array - os mesmos bytes subjacentes, com conveniências adicionais específicas do Node sobrepostas, não um tipo de dado separado.
"Buffer.allocUnsafe é apenas uma versão mais rápida e equivalente de Buffer.alloc." Ele pula completamente o preenchimento com zero, então a memória retornada pode conter temporariamente dados residuais de uso anterior - é mais rápido especificamente porque troca essa garantia, não é uma atualização gratuita.
"Fatiar um Buffer copia seus dados."buf.subarray() (e o legado buf.slice()) retornam uma visualização sobre a mesma memória por padrão - escrever através da fatia pode mutar os bytes do Buffer original também.
O que exatamente é um Buffer, em nível de memória?
Uma sequência de comprimento fixo de valores de byte único apoiada por memória alocada fora do heap JavaScript com coleta de lixo do V8, exposta ao seu código através de uma API que é um superconjunto do Uint8Array padrão.
Por que o Node não usou arrays JavaScript regulares para dados binários?
Arrays regulares contêm valores JavaScript arbitrários (floats por padrão) com comprimento dinâmico e memória gerenciada pelo heap - nada disso é adequado para dados binários brutos, de tamanho fixo e restritos a bytes de forma eficiente, então o Node precisou de um tipo feito sob medida antes que o JavaScript tivesse seus próprios TypedArrays.
Como o Buffer se relaciona com `ArrayBuffer` e `TypedArray`?
ArrayBuffer é o bloco de memória bruta; um TypedArray (como Uint8Array) é uma visualização tipada sobre parte ou toda essa memória; Buffer é a própria subclasse do Node de Uint8Array, adicionando métodos de conveniência enquanto permanece totalmente compatível com o maquinário padrão TypedArray.
Um Buffer sabe qual codificação de texto ele contém?
Não - um Buffer é apenas bytes sem metadados anexados sobre codificação; você fornece a codificação explicitamente toda vez que converte de ou para uma string (buf.toString('utf8'), Buffer.from(str, 'utf8')), e o Node confia em qualquer codificação que você nomear.
Por que `buf.length` às vezes é diferente da contagem de caracteres da string?
Porque buf.length conta bytes, e codificações como UTF-8 usam um número variável de bytes por caractere - caracteres ASCII levam um byte, mas muitos caracteres não ASCII levam dois, três ou quatro, então a contagem de bytes e a contagem de caracteres só coincidem para conteúdo puramente ASCII.
Qual é a diferença real entre `Buffer.alloc` e `Buffer.allocUnsafe`?
Buffer.alloc(n) preenche a memória com zeros antes de retorná-la, garantindo bytes limpos a um pequeno custo de desempenho; Buffer.allocUnsafe(n) pula essa etapa e recicla memória de um pool interno para velocidade, o que significa que os bytes retornados podem conter temporariamente dados residuais não relacionados até que você os sobrescreva.
Fatiar um Buffer é caro?
Não, e é precisamente porque é barato que pode surpreendê-lo - subarray()/slice() retornam uma visualização sobre a mesma memória em vez de copiá-la, então a operação em si é rápida, mas as escritas através da fatia afetam o Buffer original também.
Devo carregar um arquivo inteiro em um Buffer ou usar um stream?
Para arquivos pequenos e limitados, um único Buffer é simples e bom; para entrada grande ou ilimitada, um stream processa os dados em blocos de tamanho fixo em vez de manter toda a carga útil na memória de uma vez, o que mantém o uso de memória previsível, independentemente do tamanho da entrada.
Buffer e os padrões da Web `TextEncoder`/`TextDecoder` estão fazendo o mesmo trabalho?
Eles se sobrepõem especificamente para o caso de conversão de texto - TextEncoder/TextDecoder lidam com a conversão de texto UTF-8 para bytes de maneira padrão da Web e portátil, enquanto Buffer é um contêiner binário de propósito geral com uma API muito mais ampla além de apenas texto.
Mutar um `Uint8Array` pode afetar um Buffer, ou vice-versa?
Sim, se eles compartilharem o mesmo ArrayBuffer subjacente - como Buffer é uma subclasse de Uint8Array e ambos são apenas visualizações sobre a memória, duas visualizações sobre o mesmo bloco veem as escritas umas das outras, elas não são cópias independentes.
Por que o Node mantém um pool de memória para alocação de Buffer?
Alocar pequenos buffers um por um do sistema operacional tem um overhead real por alocação; o pooling pré-reserva um bloco maior e distribui fatias dele para pequenas alocações, o que é mais rápido, mas é exatamente o mecanismo que torna o risco de memória desatualizada do allocUnsafe possível.