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, ao ler de um arquivo com fs.readFile()
, os dados retornados para o callback ou Promise são um objeto buffer . Além disso, ao fazer solicitações HTTP no 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 no Node.js.
Neste tutorial, você usará o Node.js REPL para percorrer vários 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á do Node.js instalado em sua máquina de desenvolvimento. Este tutorial usa a versão 10.19.0. Para instalar isso no macOS ou no Ubuntu 18.04, siga as etapas em Como Instalar o Node.js e Criar um Ambiente de Desenvolvimento Local no macOS ou na 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 você deseja uma atualizaçã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 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 irá mostrar as duas maneiras principais de criar um objeto buffer no Node.js.
Para decidir qual método usar, você precisa responder a esta pergunta: Você deseja criar um novo buffer ou extrair um buffer a partir de dados existentes? Se você for armazenar dados na memória que ainda não recebeu, você vai querer 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, nós digitaríamos isso no console:
Para criar um novo buffer, utilizamos a classe globalmente disponível Buffer
, que possui o método alloc()
. Ao fornecer 1024
como argumento para alloc()
, criamos um buffer com 1KB de tamanho.
Por padrão, ao inicializar um buffer com alloc()
, o buffer é preenchido com zeros binários como espaço reservado para dados futuros. 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 referencia um espaço na memória que armazena 1KB 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 do ASCII, o Código Padrão Americano para Intercâmbio de Informações. ASCII pode codificar bytes com letras maiúsculas e minúsculas em 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.
Nota: 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 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 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 diz ao JavaScript como converter o objeto para 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 experimentamos criar buffers, podemos mergulhar em exemplos de leitura de seus dados.
Passo 2 — Lendo de um Buffer
Há muitas maneiras de acessar dados em um Buffer. Podemos acessar um byte individual em um buffer ou podemos extrair o conteúdo inteiro.
Para acessar um byte de um buffer, passamos o índice ou a localização do byte que desejamos. 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 tivermos uma sequência de 8 bits e dois valores possíveis 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. Digite o seguinte no REPL:
O REPL retorna 105
, que representa o i
minúsculo.
Por fim, vamos pegar o terceiro caractere:
Você verá 33
exibido no REPL, o 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 um array 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 todo o conteúdo 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 ao usuário. Se usarmos este método em hiBuf
, obteremos a string Hi!
. Vamos tentar!
Na solicitação, insira:
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 com 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()
tem um parâmetro opcional, encoding
. Podemos usar este parâmetro para alterar a codificação dos dados do buffer que é retornado.
Por exemplo, se você quiser a codificação hexadecimal para hiBuf
, você entraria o seguinte no prompt:
Essa declaraçã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()
comporta-se de forma diferente. Independentemente se o buffer foi 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()
. No prompt, digite:
O REPL retornará:
Output{ type: 'Buffer', data: [ 72, 105, 33 ] }
O objeto JSON possui uma propriedade type
que será sempre Buffer
. Isso permite que os programas distingam 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 puxamos os bytes individualmente.
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 como observado 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.
Etapa 3 — Modificando um Buffer
Há muitas maneiras de modificar um objeto buffer existente. Semelhante à leitura, podemos modificar bytes do 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 mudar 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. Prossiga chamando o método toString()
:
Ele 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 letra e
; em vez 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:
O seu REPL irá 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 de todo o buffer, 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 terminal 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 a codificação UTF-8, que usa um byte para cada caractere. Se o buffer usasse a 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 só aceitará os bytes que cabem. 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 primeiros três bytes:
O REPL retorna:
Output'Cat'
A função write()
adiciona os bytes em ordem sequencial, então apenas os primeiros três bytes foram colocados no buffer.
Em contraste, 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 do que o conteúdo original:
Como os buffers escrevem sequencialmente, começando do 0
, se imprimirmos o conteúdo do buffer:
Seremos 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 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!
. Vamos usar copy()
para obter Nananana
de wordsBuf
para catchphraseBuf
.
Para copiar dados de um buffer para o outro, vamos usar 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 planejado. 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 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 obrigató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 a ser o comprimento dos buffers.
Na prompt do REPL, copie o conteúdo de wordsBuf
assim:
O REPL confirma que 8
bytes foram escritos. Note 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
ao copiar o conteúdo de wordsBuf
.
Você pode sair do REPL do Node.js se desejar. Note que todas as variáveis que foram criadas não estarão mais disponíveis quando você fizer isso:
Conclusão
Neste tutorial, você aprendeu que os 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, leu dados de um buffer examinando seus bytes individuais e usando os métodos toString()
e toJSON()
. Finalmente, modificou os dados armazenados por um buffer alterando seus bytes individuais e usando os métodos write()
e copy()
.
Os buffers fornecem uma ótima visão de 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 estejam codificados em UTF-8 ou ASCII e observar a diferença no tamanho. Você também pode pegar um buffer com UTF-8 e usar toString()
para convertê-lo em 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 quiser continuar aprendendo Node.js, 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 sobre Node.
Source:
https://www.digitalocean.com/community/tutorials/using-buffers-in-node-js