O autor selecionou o Fundo de Auxílio COVID-19 para receber uma doação como parte do programa Escreva para Doações.
Introdução
A buffer is a space in memory (typically RAM) that stores binary data. In Node.js, we can access these spaces of memory with the built-in Buffer
class. Buffers store a sequence of integers, similar to an array in JavaScript. Unlike arrays, you cannot change the size of a buffer once it is created.
Você pode ter usado buffers implicitamente se já escreveu código Node.js. Por exemplo, quando você lê de um arquivo com fs.readFile()
, os dados retornados para o callback ou Promise são um objeto buffer . Além disso, quando são feitas solicitações HTTP em Node.js, elas retornam fluxos de dados que são temporariamente armazenados em um buffer interno quando o cliente não pode processar o fluxo de uma só vez.
Buffers são úteis quando você está interagindo com dados binários, geralmente em níveis de rede mais baixos. Eles também equipam você com a capacidade de fazer manipulação de dados refinada em Node.js.
Neste tutorial, você usará o Node.js REPL para percorrer diversos exemplos de buffers, como criar buffers, ler de buffers, escrever em buffers, copiar de buffers e usar buffers para converter entre dados binários e codificados. No final do tutorial, você terá aprendido como usar a classe Buffer
para trabalhar com dados binários.
Pré-requisitos
- Você precisará ter o Node.js instalado em sua máquina de desenvolvimento. Este tutorial usa a versão 10.19.0. Para instalá-lo no macOS ou Ubuntu 18.04, siga as etapas em Como Instalar o Node.js e Criar um Ambiente de Desenvolvimento Local no macOS ou a seção Instalando Usando um PPA de Como Instalar o Node.js no Ubuntu 18.04.
- Neste tutorial, você interagirá com buffers no Node.js REPL (Read-Evaluate-Print-Loop). Se desejar uma revisão sobre como usar o Node.js REPL de forma eficaz, você pode ler nosso guia sobre Como Usar o Node.js REPL.
- Para este artigo, esperamos que o usuário esteja confortável com o JavaScript básico e seus tipos de dados. Você pode aprender esses fundamentos com nossa série Como Codificar em JavaScript.
Passo 1 — Criando um Buffer
Este primeiro passo mostrará as duas principais maneiras de criar um objeto de buffer no Node.js.
Para decidir qual método usar, você precisa responder a esta pergunta: Você quer criar um novo buffer ou extrair um buffer de dados existentes? Se você pretende armazenar dados na memória que ainda não recebeu, precisará criar um novo buffer. No Node.js, usamos a função alloc()
da classe Buffer
para fazer isso.
Vamos abrir o REPL do Node.js para ver por nós mesmos. No seu terminal, digite o comando node
:
Você verá o prompt começar com >
.
A função alloc()
recebe o tamanho do buffer como seu primeiro e único argumento obrigatório. O tamanho é um número inteiro que representa quantos bytes de memória o objeto buffer usará. Por exemplo, se quisermos criar um buffer que tenha 1KB (quilobyte) de tamanho, equivalente a 1024 bytes, entraríamos isso no console:
Para criar um novo buffer, usamos a classe globalmente disponível Buffer
, que possui o método alloc()
. Ao fornecer 1024
como argumento para alloc()
, criamos um buffer com 1 KB de tamanho.
Por padrão, ao inicializar um buffer com alloc()
, o buffer é preenchido com zeros binários como espaço reservado para dados posteriores. No entanto, podemos alterar o valor padrão se desejarmos. Se quisermos criar um novo buffer com 1
s em vez de 0
s, definiríamos o segundo parâmetro da função alloc()
— fill
.
No seu terminal, crie um novo buffer no prompt do REPL preenchido com 1
s:
Acabamos de criar um novo objeto de buffer que faz referência a um espaço na memória que armazena 1 KB de 1
s. Embora tenhamos inserido um número inteiro, todos os dados armazenados em um buffer são dados binários.
Dados binários podem vir em muitos formatos diferentes. Por exemplo, vamos considerar uma sequência binária representando um byte de dados: 01110110
. Se esta sequência binária representasse uma string em inglês usando o padrão de codificação ASCII, seria a letra v
. No entanto, se nosso computador estivesse processando uma imagem, essa sequência binária poderia conter informações sobre a cor de um pixel.
O computador sabe processá-los de forma diferente porque os bytes são codificados de maneira diferente. A codificação de bytes é o formato do byte. Um buffer no Node.js usa o esquema de codificação UTF-8 por padrão se for inicializado com dados de string. Um byte em UTF-8 representa um número, uma letra (em inglês e em outros idiomas) ou um símbolo. UTF-8 é um superconjunto de ASCII, o Código Padrão Americano para Intercâmbio de Informações. ASCII pode codificar bytes com letras maiúsculas e minúsculas do alfabeto inglês, os números 0-9 e alguns outros símbolos como o ponto de exclamação (!) ou o sinal de ampersand (&).
Se estivéssemos escrevendo um programa que só pudesse trabalhar com caracteres ASCII, poderíamos alterar a codificação usada pelo nosso buffer com o terceiro argumento da função alloc()
— encoding
.
Vamos criar um novo buffer que tenha cinco bytes de comprimento e armazene apenas caracteres ASCII:
O buffer é inicializado com cinco bytes do caractere a
, usando a representação ASCII.
Observação: Por padrão, o Node.js suporta as seguintes codificações de caracteres:
- ASCII, representado como
ascii
- UTF-8, representado como
utf-8
ouutf8
- UTF-16, representado como
utf-16le
ouutf16le
- UCS-2, representado como
ucs-2
ouucs2
- Base64, representado como
base64
- Hexadecimal, representado como
hex
- ISO/IEC 8859-1, representado como
latin1
oubinary
Todos esses valores podem ser usados nas funções da classe Buffer que aceitam um parâmetro de encoding
. Portanto, esses valores são todos válidos para o método alloc()
.
Até agora, temos criado novos buffers com a função alloc()
. Mas às vezes podemos querer criar um buffer a partir de dados que já existem, como uma string ou um array.
Para criar um buffer a partir de dados pré-existentes, usamos o método from()
. Podemos usar essa função para criar buffers a partir de:
- Um array de inteiros: Os valores inteiros podem estar entre
0
e255
. - Um
ArrayBuffer
: Este é um objeto JavaScript que armazena um comprimento fixo de bytes. - A string.
- Outro buffer.
- Outros objetos JavaScript que possuem uma propriedade
Symbol.toPrimitive
. Essa propriedade informa ao JavaScript como converter o objeto em um tipo de dado primitivo:boolean
,null
,undefined
,number
,string
ousymbol
. Você pode ler mais sobre Símbolos na documentação do JavaScript da Mozilla.
Vamos ver como podemos criar um buffer a partir de uma string. No prompt do Node.js, insira o seguinte:
Agora temos um objeto buffer criado a partir da string Meu nome é Paul
. Vamos criar um novo buffer a partir de outro buffer que fizemos anteriormente:
Agora criamos um novo buffer asciiCopy
que contém os mesmos dados que asciiBuf
.
Agora que tivemos experiência em criar buffers, podemos explorar exemplos de leitura de seus dados.
Passo 2 — Leitura de um Buffer
Há muitas maneiras de acessar dados em um Buffer. Podemos acessar um byte individual em um buffer ou podemos extrair todo o conteúdo.
Para acessar um byte de um buffer, passamos o índice ou a localização do byte desejado. Buffers armazenam dados sequencialmente como arrays. Eles também indexam seus dados como arrays, começando em 0
. Podemos usar a notação de array no objeto buffer para obter um byte individual.
Vamos ver como isso fica criando um buffer a partir de uma string no REPL:
Agora vamos ler o primeiro byte do buffer:
Ao pressionar ENTER
, o REPL exibirá:
Output72
O inteiro 72
corresponde à representação UTF-8 da letra H
.
Nota: Os valores para bytes podem ser números entre 0
e 255
. Um byte é uma sequência de 8 bits. Um bit é binário, e portanto só pode ter um dos dois valores: 0
ou 1
. Se temos uma sequência de 8 bits e dois possíveis valores por bit, então temos um máximo de 2⁸ valores possíveis para um byte. Isso resulta em um máximo de 256 valores. Como começamos a contar a partir de zero, isso significa que nosso número mais alto é 255.
Vamos fazer o mesmo para o segundo byte. Insira o seguinte no REPL:
O REPL retorna 105
, que representa o i
minúsculo.
Finalmente, vamos pegar o terceiro caractere:
Você verá 33
exibido no REPL, que corresponde a !
.
Vamos tentar recuperar um byte de um índice inválido:
O REPL retornará:
Outputundefined
Isso é como se tentássemos acessar um elemento em uma matriz com um índice incorreto.
Agora que vimos como ler bytes individuais de um buffer, vamos ver nossas opções para recuperar todos os dados armazenados em um buffer de uma vez. O objeto buffer vem com os métodos toString()
e toJSON()
, que retornam o conteúdo inteiro de um buffer em dois formatos diferentes.
Como o nome sugere, o método toString()
converte os bytes do buffer em uma string e a retorna para o usuário. Se usarmos este método em hiBuf
, obteremos a string Hi!
. Vamos tentar!
No prompt, digite:
O REPL retornará:
Output'Hi!'
Esse buffer foi criado a partir de uma string. Vamos ver o que acontece se usarmos o toString()
em um buffer que não foi feito a partir de dados de string.
Vamos criar um novo buffer vazio que tenha 10
bytes de tamanho:
Agora, vamos usar o método toString()
:
Veremos o seguinte resultado:
'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
A string \u0000
é o caractere Unicode para NULL
. Corresponde ao número 0
. Quando os dados do buffer não estão codificados como uma string, o método toString()
retorna a codificação UTF-8 dos bytes.
O toString()
possui um parâmetro opcional, encoding
. Podemos usar este parâmetro para mudar a codificação dos dados do buffer que são retornados.
Por exemplo, se você quisesse a codificação hexadecimal para hiBuf
, você digitaria o seguinte no prompt:
Essa instrução será avaliada como:
Output'486921'
486921
é a representação hexadecimal para os bytes que representam a string Hi!
. No Node.js, quando os usuários querem converter a codificação de dados de uma forma para outra, geralmente colocam a string em um buffer e chamam toString()
com a codificação desejada.
O método toJSON()
se comporta de forma diferente. Independentemente de o buffer ter sido feito a partir de uma string ou não, ele sempre retorna os dados como a representação inteira do byte.
Vamos reutilizar os buffers hiBuf
e tenZeroes
para praticar o uso do toJSON()
. Na linha de comando, insira:
O REPL retornará:
Output{ type: 'Buffer', data: [ 72, 105, 33 ] }
O objeto JSON tem uma propriedade type
que sempre será Buffer
. Isso é para que os programas possam distinguir esses objetos JSON de outros objetos JSON.
A propriedade data
contém uma matriz da representação inteira dos bytes. Você pode ter notado que 72
, 105
e 33
correspondem aos valores que recebemos quando extraímos individualmente os bytes.
Vamos tentar o método toJSON()
com tenZeroes
:
No REPL, você verá o seguinte:
Output{ type: 'Buffer', data: [
0, 0, 0, 0, 0,
0, 0, 0, 0, 0
] }
O type
é o mesmo que mencionado anteriormente. No entanto, os dados agora são uma matriz com dez zeros.
Agora que cobrimos as principais maneiras de ler de um buffer, vamos ver como modificamos o conteúdo de um buffer.
Passo 3 — Modificando um Buffer
Há muitas maneiras de modificar um objeto de buffer existente. Semelhante à leitura, podemos modificar bytes de buffer individualmente usando a sintaxe de array. Também podemos escrever novos conteúdos em um buffer, substituindo os dados existentes.
Vamos começar olhando como podemos alterar bytes individuais de um buffer. Lembre-se da nossa variável de buffer hiBuf
, que contém a string Hi!
. Vamos alterar cada byte para que contenha Hey
em vez disso.
No REPL, vamos primeiro tentar definir o segundo elemento de hiBuf
para e
:
Agora, vamos ver este buffer como uma string para confirmar que está armazenando os dados corretos. Siga chamando o método toString()
:
Será avaliado como:
Output'H\u0000!'
Recebemos essa saída estranha porque o buffer só pode aceitar um valor inteiro. Não podemos atribuir a ele a letra e
; ao invés disso, temos que atribuir o número cujo equivalente binário representa e
:
Agora, quando chamamos o método toString()
:
Obtemos esta saída no REPL:
Output'He!'
Para mudar o último caractere no buffer, precisamos definir o terceiro elemento para o inteiro que corresponde ao byte para y
:
Vamos confirmar usando o método toString()
mais uma vez:
Seu REPL exibirá:
Output'Hey'
Se tentarmos escrever um byte que está fora do intervalo do buffer, ele será ignorado e o conteúdo do buffer não mudará. Por exemplo, vamos tentar definir o quarto elemento inexistente do buffer para o
:
Podemos confirmar que o buffer não foi alterado com o método toString()
:
A saída ainda é:
Output'Hey'
Se quisermos alterar o conteúdo do buffer inteiro, podemos usar o método write()
. O método write()
aceita uma string que substituirá o conteúdo de um buffer.
Vamos usar o método write()
para alterar o conteúdo de hiBuf
de volta para Hi!
. No seu shell Node.js, digite o seguinte comando no prompt:
O método write()
retornou 3
no REPL. Isso ocorre porque ele escreveu três bytes de dados. Cada letra tem um byte de tamanho, já que esse buffer usa codificação UTF-8, que usa um byte para cada caractere. Se o buffer usasse codificação UTF-16, que tem um mínimo de dois bytes por caractere, então a função write()
teria retornado 6
.
Agora verifique o conteúdo do buffer usando toString()
:
O REPL produzirá:
Output'Hi!'
Isso é mais rápido do que ter que mudar cada elemento byte a byte.
Se você tentar escrever mais bytes do que o tamanho de um buffer, o objeto de buffer aceitará apenas os bytes que couberem. Para ilustrar, vamos criar um buffer que armazena três bytes:
Agora vamos tentar escrever Cats
nele:
Quando a chamada de write()
é avaliada, o REPL retorna 3
, indicando que apenas três bytes foram escritos no buffer. Agora confirme que o buffer contém os três primeiros bytes:
O REPL retorna:
Output'Cat'
A função write()
adiciona os bytes em ordem sequencial, então apenas os três primeiros bytes foram colocados no buffer.
Por outro lado, vamos criar um Buffer
que armazena quatro bytes:
Escreva o mesmo conteúdo nele:
Em seguida, adicione algum conteúdo novo que ocupa menos espaço que o conteúdo original:
Como os buffers escrevem sequencialmente, começando do 0
, se imprimirmos o conteúdo do buffer:
Seríamos recebidos com:
Output'Hits'
Os dois primeiros caracteres são sobrescritos, mas o restante do buffer permanece intocado.
Às vezes, os dados que queremos em nosso buffer preexistente não estão em uma string, mas residem em outro objeto de buffer. Nestes casos, podemos usar a função copy()
para modificar o que nosso buffer está armazenando.
Vamos criar dois novos buffers:
Os buffers wordsBuf
e catchphraseBuf
ambos contêm dados de string. Queremos modificar catchphraseBuf
para que ele armazene Nananana Tartaruga!
em vez de Não tenho certeza Tartaruga!
. Usaremos copy()
para obter Nananana
de wordsBuf
para catchphraseBuf
.
Para copiar dados de um buffer para o outro, usaremos o método copy()
no buffer que é a fonte das informações. Portanto, como wordsBuf
possui os dados de string que queremos copiar, precisamos copiar assim:
O parâmetro target
neste caso é o buffer catchphraseBuf
.
Quando inserimos isso no REPL, ele retorna 15
, indicando que 15 bytes foram escritos. A string Nananana
usa apenas 8 bytes de dados, então imediatamente sabemos que nossa cópia não ocorreu conforme o esperado. Use o método toString()
para ver o conteúdo de catchphraseBuf
:
O REPL retorna:
Output'Banana Nananana!'
Por padrão, copy()
pegou todo o conteúdo de wordsBuf
e o colocou em catchphraseBuf
. Precisamos ser mais seletivos para alcançar nosso objetivo e apenas copiar Nananana
. Vamos reescrever o conteúdo original de catchphraseBuf
antes de continuar:
A função copy()
tem alguns parâmetros adicionais que nos permitem personalizar quais dados são copiados para o outro buffer. Aqui está uma lista de todos os parâmetros desta função:
target
– Este é o único parâmetro necessário decopy()
. Como vimos em nosso uso anterior, é o buffer para o qual queremos copiar.targetStart
– Este é o índice dos bytes no buffer de destino onde devemos começar a copiar. Por padrão, é0
, o que significa que copia dados a partir do início do buffer.sourceStart
– Este é o índice dos bytes no buffer de origem de onde devemos copiar.sourceEnd
– Este é o índice dos bytes no buffer de origem onde devemos parar de copiar. Por padrão, é o comprimento do buffer.
Então, para copiar Nananana
de wordsBuf
para catchphraseBuf
, nosso alvo
deve ser catchphraseBuf
como antes. O targetStart
seria 0
pois queremos que Nananana
apareça no início de catchphraseBuf
. O sourceStart
deve ser 7
pois é o índice onde Nananana
começa em wordsBuf
. O sourceEnd
continuaria sendo o comprimento dos buffers.
No prompt do REPL, copie o conteúdo de wordsBuf
assim:
O REPL confirma que 8
bytes foram escritos. Observe como wordsBuf.length
é usado como valor para o parâmetro sourceEnd
. Assim como em arrays, a propriedade length
nos dá o tamanho do buffer.
Agora vamos ver o conteúdo de catchphraseBuf
:
O REPL retorna:
Output'Nananana Turtle!'
Sucesso! Conseguimos modificar os dados de catchphraseBuf
copiando o conteúdo de wordsBuf
.
Você pode sair do Node.js REPL se desejar. Note que todas as variáveis que foram criadas não estarão mais disponíveis quando o fizer:
Conclusão
Neste tutorial, você aprendeu que buffers são alocações de comprimento fixo na memória que armazenam dados binários. Primeiro, você criou buffers definindo seu tamanho na memória e inicializando-os com dados pré-existentes. Em seguida, você leu dados de um buffer examinando seus bytes individuais e usando os métodos toString()
e toJSON()
. Finalmente, você modificou os dados armazenados por um buffer alterando seus bytes individuais e usando os métodos write()
e copy()
.
Buffers oferecem grande insight sobre como os dados binários são manipulados pelo Node.js. Agora que você pode interagir com buffers, pode observar as diferentes maneiras como a codificação de caracteres afeta como os dados são armazenados. Por exemplo, você pode criar buffers a partir de dados de string que não sejam de codificação UTF-8 ou ASCII e observar a diferença em tamanho. Você também pode pegar um buffer com UTF-8 e usar toString()
para convertê-lo para outros esquemas de codificação.
Para aprender sobre buffers no Node.js, você pode ler a documentação do Node.js sobre o objeto Buffer
. Se você gostaria de continuar aprendendo Node.js, você pode retornar para a série Como Codificar em Node.js, ou navegar por projetos de programação e configurações em nossa página de tópicos do Node.
Source:
https://www.digitalocean.com/community/tutorials/using-buffers-in-node-js