Node.js Buffers
O que é um buffer?
Um buffer é uma área de memória. Desenvolvedores JavaScript não são familiarizados com esse conceito, muito menos que desenvolvedores C, C++ ou Go (ou qualquer programador que use uma linguagem de programação de sistema), que interagem com memória diariamente.
Ele representa um pedaço de memória de tamanho fixo (não pode ser redimensionado) alocado fora da engine JavaScript V8.
Você pode pensar em um buffer com um array de inteiros, em que cada um representa um byte de dados.
Ele é implementado pela classe Buffer no Node.js.
Por que precisamos de um buffer?
Buffers foram introduzidos para ajudar desenvolvedores a lidar com dados binários, em um ecossistema que tradicionalmente só lida com strings em vez de binários.
Buffers são profundamente linkados com streams. Quando um processador de stram recebe mais rápido do que pode aguentar, ele coloca os dados em um buffer.
Uma visualização sinples de um buffer é quando você está assistindo um vídeo no Youtube e a linha vermelha vai além do seu ponto de visualização: você está baixando dados mais rápido do que está os vendo, e o seu browser os bufferiza.
Como criar um buffer
Um buffer é criado usando os métodos Buffer.from()
, Buffer.alloc()
, e Buffer.allocUnsafe()
.
const buf = Buffer.from('Hey!')
Buffer.from(array)
Buffer.from(arrayBuffer[, byteOffset[, length]])
Buffer.from(buffer)
Buffer.from(string[, encoding])
Você também pode inicializar passando apenas o tamanho. Isso cria um buffer de 1KB:
const buf = Buffer.alloc(1024)//orconst buf = Buffer.allocUnsafe(1024)
Enquanto ambos alloc
e allocUnsafe
alocam um Buffer
do tamanho especificado em bytes, o Buffer
criado via alloc
vai ser inicializado com zeros e o criado via allocUnsafe
vai ser não inicializado. Isso significa que enquanto allocUnsafe
pode ser bem rápido em comparação ao alloc
, o segmento de memória alocado pode conter dados antigos que podem ser potencialmente sensíveis.
Dados antigos, se presentes em memória, podem ser acessados ou vazados quando a memória do Buffer
é lida. Isso é o que realmente faz do allocUnsafe
inseguro e tenha cuidado extra ao usá-lo.
Usando um buffer
Acessando o conteúdo de um buffer
Um buffer, sendo um array de bytes, pode ser acessado como um array:
const buf = Buffer.from('Hey!')console.log(buf[0]) // 72console.log(buf[1]) // 101console.log(buf[2]) // 121
Esses números são o Código Unicode que identifica o caracter naquela posição do buffer (H => 72, e => 101, y => 121)
Você pode imprimir o conteúdo completo de um buffer usando o método toString()
:
console.log(buf.toString())
Observe que se você inicializar um buffer com um número que defina o tamanho dele, você vai ter acesso à memória pre-inicializada, que vai conter dados aleatórios, não um buffer vazio!
Obtendo o comprimento de um buffer
Use a propriedade length
:
const buf = Buffer.from('Hey!')console.log(buf.length)
Iterando sob o conteúdo de um buffer
const buf = Buffer.from('Hey!')for (const item of buf) {console.log(item) // 72 101 121 33}
Mudando o conteúdo de um buffer
Você pode gravar uma string inteira de dados em um buffer usando o método write()
:
const buf = Buffer.alloc(4)buf.write('Hey!')
Assim como você pode acessar um buffer com uma sintaxe de array, você também pode definir os conteúdos do buffer dessa mesma forma:
const buf = Buffer.from('Hey!')buf[1] = 111 // oconsole.log(buf.toString()) // Hoy!
Copiando um buffer
É possível copiar um buffer usando o método copy()
:
const buf = Buffer.from('Hey!')let bufcopy = Buffer.alloc(4) // allocate 4 bytesbuf.copy(bufcopy)
Por padrão você copia o buffer inteiro. Mais 3 parâmetros deixam você definir a posição inicial, a posição final, e o comprimento do novo buffer:
const buf = Buffer.from('Hey!')let bufcopy = Buffer.alloc(2) // allocate 2 bytesbuf.copy(bufcopy, 0, 0, 2)bufcopy.toString() // 'He'
Fatiando um buffer
Se você quer criar uma visualização parccial de um buffer, você pode criar uma fatia (slice). Uma fatia não é uma cópia: o buffer original ainda é a fonte de verdade. Se ele mudar, sua fatia também muda.
Use o método slice()
para criar a fatia. O primeiro parâmetro é a posição inicial, e você pode especificar um segundo parâmetro opcional com a posição final:
const buf = Buffer.from('Hey!')buf.slice(0).toString() // Hey!const slice = buf.slice(0, 2)console.log(slice.toString()) // Hebuf[1] = 111 // oconsole.log(slice.toString()) // Ho