El autor seleccionó el Fondo de Ayuda COVID-19 para recibir una donación como parte del programa Escribir para Donaciones.
Introducción
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.
Es posible que hayas utilizado búferes implícitamente si ya has escrito código Node.js. Por ejemplo, cuando lees desde un archivo con fs.readFile()
, los datos devueltos al callback o Promise son un objeto de búfer . Además, cuando se realizan solicitudes HTTP en Node.js, devuelven flujos de datos que se almacenan temporalmente en un búfer interno cuando el cliente no puede procesar el flujo de datos de una sola vez.
Los búferes son útiles cuando interactúas con datos binarios, generalmente en niveles de red más bajos. También te equipan con la capacidad de hacer manipulación de datos finamente detallada en Node.js.
En este tutorial, utilizarás el Node.js REPL para ejecutar varios ejemplos de búferes, como la creación de búferes, la lectura desde búferes, la escritura en búferes, la copia desde búferes y el uso de búferes para convertir entre datos binarios y codificados. Al final del tutorial, habrás aprendido cómo usar la clase Buffer
para trabajar con datos binarios.
Prerrequisitos
- Necesitarás tener Node.js instalado en tu máquina de desarrollo. Este tutorial utiliza la versión 10.19.0. Para instalarla en macOS o Ubuntu 18.04, sigue los pasos en Cómo Instalar Node.js y Crear un Entorno de Desarrollo Local en macOS o la sección Instalación Usando un PPA de Cómo Instalar Node.js en Ubuntu 18.04.
- En este tutorial, interactuarás con búferes en el Node.js REPL (Read-Evaluate-Print-Loop). Si deseas repasar cómo utilizar el REPL de Node.js de manera efectiva, puedes leer nuestra guía sobre Cómo Usar el REPL de Node.js.
- Para este artículo esperamos que el usuario esté cómodo con JavaScript básico y sus tipos de datos. Puedes aprender esos fundamentos con nuestra serie Cómo Codificar en JavaScript.
Paso 1 — Crear un Buffer
Este primer paso te mostrará las dos formas principales de crear un objeto buffer en Node.js.
Para decidir qué método usar, necesitas responder a esta pregunta: ¿Quieres crear un nuevo buffer o extraer un buffer de datos existentes? Si vas a almacenar datos en la memoria que aún no has recibido, querrás crear un nuevo buffer. En Node.js usamos la función alloc()
de la clase Buffer
para hacer esto.
Veamos por nosotros mismos abriendo el REPL de Node.js. En tu terminal, ingresa el comando node
:
Verás que el prompt comienza con >
.
La función alloc()
toma el tamaño del buffer como su primer y único argumento requerido. El tamaño es un número entero que representa cuántos bytes de memoria usará el objeto buffer. Por ejemplo, si quisiéramos crear un buffer que fuera de 1KB (kilobyte), equivalente a 1024 bytes, lo ingresaríamos en la consola:
Para crear un nuevo búfer, utilizamos la clase globalmente disponible Buffer
, que tiene el método alloc()
. Al proporcionar 1024
como argumento para alloc()
, creamos un búfer que tiene un tamaño de 1 KB.
De forma predeterminada, al inicializar un búfer con alloc()
, el búfer se llena con ceros binarios como marcador de posición para datos posteriores. Sin embargo, podemos cambiar el valor predeterminado si así lo deseamos. Si quisiéramos crear un nuevo búfer con 1
s en lugar de 0
s, estableceríamos el segundo parámetro de la función alloc()
, que es fill
.
En tu terminal, crea un nuevo búfer en el prompt del REPL que esté lleno de 1
s:
Acabamos de crear un nuevo objeto de búfer que hace referencia a un espacio en memoria que almacena 1 KB de 1
s. Aunque ingresamos un número entero, todos los datos almacenados en un búfer son datos binarios.
Los datos binarios pueden tener muchos formatos diferentes. Por ejemplo, consideremos una secuencia binaria que representa un byte de datos: 01110110
. Si esta secuencia binaria representara una cadena en inglés utilizando el estándar de codificación ASCII, sería la letra v
. Sin embargo, si nuestra computadora estuviera procesando una imagen, esa secuencia binaria podría contener información sobre el color de un píxel.
La computadora sabe procesarlos de manera diferente porque los bytes están codificados de manera diferente. La codificación de bytes es el formato del byte. Un búfer en Node.js utiliza el esquema de codificación UTF-8 de forma predeterminada si se inicializa con datos de cadena. Un byte en UTF-8 representa un número, una letra (en inglés y en otros idiomas) o un símbolo. UTF-8 es un superconjunto de ASCII, el Código Estándar Americano para el Intercambio de Información. ASCII puede codificar bytes con letras mayúsculas y minúsculas del inglés, los números del 0 al 9 y algunos otros símbolos como el signo de exclamación (!) o el signo de ampersand (&).
Si estuviéramos escribiendo un programa que solo pudiera trabajar con caracteres ASCII, podríamos cambiar la codificación utilizada por nuestro búfer con el tercer argumento de la función alloc()
– encoding
.
Creemos un nuevo búfer que tenga cinco bytes de longitud y almacene solo caracteres ASCII:
El búfer se inicializa con cinco bytes del carácter a
, usando la representación ASCII.
Nota: De forma predeterminada, Node.js admite las siguientes codificaciones de caracteres:
- ASCII, representado como
ascii
- UTF-8, representado como
utf-8
outf8
- UTF-16, representado como
utf-16le
outf16le
- UCS-2, representado como
ucs-2
oucs2
- Base64, representado como
base64
- Hexadecimal, representado como
hex
- ISO/IEC 8859-1, representado como
latin1
obinary
Todos estos valores se pueden utilizar en funciones de la clase Buffer que aceptan un parámetro encoding
. Por lo tanto, todos estos valores son válidos para el método alloc()
.
Hasta ahora hemos estado creando nuevos buffers con la función alloc()
. Pero a veces podemos querer crear un buffer a partir de datos que ya existen, como una cadena o un array.
Para crear un buffer a partir de datos preexistentes, usamos el método from()
. Podemos usar esa función para crear buffers a partir de:
- Un array de enteros: Los valores enteros pueden estar entre
0
y255
. - Un
ArrayBuffer
: Este es un objeto de JavaScript que almacena una longitud fija de bytes. - A string.
- Otro buffer.
- Otros objetos de JavaScript que tienen una propiedad
Symbol.toPrimitive
. Esa propiedad le dice a JavaScript cómo convertir el objeto a un tipo de dato primitivo:boolean
,null
,undefined
,number
,string
osymbol
. Puedes leer más sobre Símbolos en la documentación de JavaScript de Mozilla.
Vamos a ver cómo podemos crear un búfer a partir de una cadena. En el prompt de Node.js, ingresa esto:
Ahora tenemos un objeto búfer creado a partir de la cadena My name is Paul
. Creemos un nuevo búfer a partir de otro búfer que hicimos anteriormente:
Ahora hemos creado un nuevo búfer asciiCopy
que contiene los mismos datos que asciiBuf
.
Ahora que hemos experimentado en la creación de búferes, podemos sumergirnos en ejemplos de cómo leer sus datos.
Paso 2 — Lectura desde un Búfer
Hay muchas formas de acceder a los datos en un búfer. Podemos acceder a un byte individual en un búfer o podemos extraer todo el contenido.
Para acceder a un byte de un búfer, pasamos el índice o la ubicación del byte que queremos. Los búferes almacenan datos secuencialmente como los arrays. También indexan sus datos como los arrays, comenzando en 0
. Podemos usar la notación de array en el objeto búfer para obtener un byte individual.
Veamos cómo se ve esto creando un búfer a partir de una cadena en el REPL:
Ahora leamos el primer byte del búfer:
Al presionar ENTER
, el REPL mostrará:
Output72
El entero 72
corresponde a la representación UTF-8 de la letra H
.
Nota: Los valores para los bytes pueden ser números entre 0
y 255
. Un byte es una secuencia de 8 bits. Un bit es binario, y por lo tanto solo puede tener uno de dos valores: 0
o 1
. Si tenemos una secuencia de 8 bits y dos posibles valores por bit, entonces tenemos un máximo de 2⁸ posibles valores para un byte. Eso significa un máximo de 256 valores. Como empezamos a contar desde cero, eso significa que nuestro número más alto es 255.
Hagamos lo mismo para el segundo byte. Ingresa lo siguiente en el REPL:
El REPL devuelve 105
, que representa la minúscula i
.
Finalmente, obtengamos el tercer carácter:
Verás 33
mostrado en el REPL, lo que corresponde a !
.
Intentemos recuperar un byte de un índice no válido:
El REPL devolverá:
Outputundefined
Esto es como si intentáramos acceder a un elemento en una matriz con un índice incorrecto.
Ahora que hemos visto cómo leer bytes individuales de un búfer, veamos nuestras opciones para recuperar todos los datos almacenados en un búfer a la vez. El objeto de búfer viene con los métodos toString()
y toJSON()
, que devuelven el contenido completo de un búfer en dos formatos diferentes.
Como su nombre indica, el método toString()
convierte los bytes del búfer en una cadena y la devuelve al usuario. Si usamos este método en hiBuf
, obtendremos la cadena ¡Hola!
. ¡Vamos a intentarlo!
En el indicador, ingrese:
El REPL devolverá:
Output'Hi!'
Ese búfer fue creado a partir de una cadena. Veamos qué sucede si usamos el toString()
en un búfer que no se creó a partir de datos de cadena.
Creemos un nuevo búfer vacío que tenga 10
bytes de tamaño:
Ahora, usemos el método toString()
:
Veremos el siguiente resultado:
'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
La cadena \u0000
es el carácter Unicode para NULO
. Corresponde al número 0
. Cuando los datos del búfer no están codificados como una cadena, el método toString()
devuelve la codificación UTF-8 de los bytes.
El toString()
tiene un parámetro opcional, encoding
. Podemos usar este parámetro para cambiar la codificación de los datos del búfer que se devuelve.
Por ejemplo, si quisieras la codificación hexadecimal para hiBuf
, ingresarías lo siguiente en el indicador:
Esa declaración se evaluará como:
Output'486921'
486921
es la representación hexadecimal de los bytes que representan la cadena ¡Hola!
. En Node.js, cuando los usuarios desean convertir la codificación de datos de una forma a otra, generalmente colocan la cadena en un búfer y llaman a toString()
con la codificación deseada.
El método toJSON()
se comporta de manera diferente. Independientemente de si el búfer se creó a partir de una cadena o no, siempre devuelve los datos como la representación entera del byte.
Volvamos a utilizar los búferes hiBuf
y tenZeroes
para practicar el uso de toJSON()
. En el indicador, ingresa:
La REPL devolverá:
Output{ type: 'Buffer', data: [ 72, 105, 33 ] }
El objeto JSON tiene una propiedad type
que siempre será Buffer
. Esto permite que los programas distingan estos objetos JSON de otros objetos JSON.
La propiedad data
contiene una matriz de la representación entera de los bytes. Puede haber notado que 72
, 105
y 33
corresponden a los valores que recibimos cuando extraímos individualmente los bytes.
Intentemos el método toJSON()
con tenZeroes
:
En la REPL verás lo siguiente:
Output{ type: 'Buffer', data: [
0, 0, 0, 0, 0,
0, 0, 0, 0, 0
] }
El type
es el mismo que se mencionó antes. Sin embargo, los datos ahora son una matriz con diez ceros.
Ahora que hemos cubierto las principales formas de leer desde un búfer, veamos cómo modificamos el contenido de un búfer.
Paso 3 — Modificando un Búfer
Hay muchas formas en que podemos modificar un objeto de búfer existente. Similar a la lectura, podemos modificar los bytes del búfer individualmente usando la sintaxis de matriz. También podemos escribir nuevos contenidos en un búfer, reemplazando los datos existentes.
Comencemos por ver cómo podemos cambiar bytes individuales de un búfer. Recordemos nuestra variable de búfer hiBuf
, que contiene la cadena ¡Hola!
. Vamos a cambiar cada byte para que contenga Hola
en su lugar.
En el REPL, primero intentemos establecer el segundo elemento de hiBuf
como e
:
Ahora, veamos este búfer como una cadena para confirmar que está almacenando los datos correctos. Continuemos llamando al método toString()
:
Se evaluará como:
Output'H\u0000!'
Recibimos esa extraña salida porque el búfer solo puede aceptar un valor entero. No podemos asignarle la letra e
; más bien, debemos asignarle el número cuyo equivalente binario representa a e
:
Ahora, cuando llamemos al método toString()
:
Obtendremos esta salida en el REPL:
Output'He!'
Para cambiar el último carácter en el búfer, necesitamos establecer el tercer elemento en el entero que corresponde al byte para y
:
Confirmemos usando nuevamente el método toString()
:
Tu REPL mostrará:
Output'Hey'
Si intentamos escribir un byte que esté fuera del rango del búfer, será ignorado y el contenido del búfer no cambiará. Por ejemplo, intentemos establecer el cuarto elemento inexistente del búfer como o
:
Podemos confirmar que el búfer no ha cambiado con el método toString()
:
La salida sigue siendo:
Output'Hey'
Si queremos cambiar el contenido de todo el búfer, podemos usar el método write()
. El método write()
acepta una cadena que reemplazará el contenido de un búfer.
Usaremos el método write()
para cambiar el contenido de hiBuf
de vuelta a ¡Hola!
. En tu shell de Node.js, escribe el siguiente comando en el prompt:
El método write()
devolvió 3
en el REPL. Esto se debe a que escribió tres bytes de datos. Cada letra tiene un tamaño de un byte, ya que este búfer utiliza codificación UTF-8, que utiliza un byte para cada carácter. Si el búfer utilizara codificación UTF-16, que tiene un mínimo de dos bytes por carácter, entonces la función write()
habría devuelto 6
.
Ahora verifica el contenido del búfer usando toString()
:
El REPL producirá:
Output'Hi!'
Esto es más rápido que tener que cambiar cada elemento byte por byte.
Si intentas escribir más bytes de los que tiene un búfer, el objeto de búfer solo aceptará los bytes que quepan. Para ilustrar, creemos un búfer que almacene tres bytes:
Ahora intentemos escribir Gatos
en él:
Cuando se evalúa la llamada a write()
, el REPL devuelve 3
, lo que indica que solo se escribieron tres bytes en el búfer. Ahora confirma que el búfer contiene los primeros tres bytes:
El REPL devuelve:
Output'Cat'
La función write()
agrega los bytes en orden secuencial, por lo que solo se colocaron los primeros tres bytes en el búfer.
Por el contrario, vamos a crear un Buffer
que almacene cuatro bytes:
Escriba el mismo contenido en él:
Luego agregue un nuevo contenido que ocupe menos espacio que el contenido original:
Dado que los búferes escriben de manera secuencial, comenzando desde 0
, si imprimimos el contenido del búfer:
Nos encontraríamos con:
Output'Hits'
Los dos primeros caracteres son sobrescritos, pero el resto del búfer permanece intacto.
A veces, los datos que queremos en nuestro búfer preexistente no están en una cadena, sino que residen en otro objeto de búfer. En estos casos, podemos usar la función copy()
para modificar lo que nuestro búfer está almacenando.
Creemos dos nuevos búferes:
Los búferes wordsBuf
y catchphraseBuf
contienen datos de cadena. Queremos modificar catchphraseBuf
para que almacene Nananana Turtle!
en lugar de Not sure Turtle!
. Usaremos copy()
para obtener Nananana
de wordsBuf
a catchphraseBuf
.
Para copiar datos de un búfer a otro, usaremos el método copy()
en el búfer que es la fuente de la información. Por lo tanto, como wordsBuf
tiene los datos de cadena que queremos copiar, necesitamos copiar de esta manera:
El parámetro target
en este caso es el búfer catchphraseBuf
.
Cuando ingresamos eso en el REPL, devuelve 15
indicando que se escribieron 15 bytes. La cadena Nananana
solo usa 8 bytes de datos, por lo que inmediatamente sabemos que nuestra copia no se realizó como se pretendía. Utilice el método toString()
para ver el contenido de catchphraseBuf
:
El REPL devuelve:
Output'Banana Nananana!'
De forma predeterminada, copy()
tomó todo el contenido de wordsBuf
y lo colocó en catchphraseBuf
. Necesitamos ser más selectivos para nuestro objetivo y solo copiar Nananana
. Reescribamos el contenido original de catchphraseBuf
antes de continuar:
La función copy()
tiene algunos parámetros más que nos permiten personalizar qué datos se copian en el otro búfer. Aquí hay una lista de todos los parámetros de esta función:
target
– Este es el único parámetro requerido decopy()
. Como hemos visto en nuestro uso anterior, es el búfer al que queremos copiar.targetStart
– Este es el índice de los bytes en el búfer de destino donde deberíamos comenzar a copiar. De forma predeterminada, es0
, lo que significa que copia datos comenzando desde el principio del búfer.sourceStart
– Este es el índice de los bytes en el búfer de origen donde deberíamos copiar desde.sourceEnd
– Este es el índice de los bytes en el búfer de origen donde deberíamos dejar de copiar. De forma predeterminada, es la longitud del búfer.
Entonces, para copiar Nananana
desde wordsBuf
hacia catchphraseBuf
, nuestro objetivo
debería ser catchphraseBuf
como antes. El targetStart
sería 0
ya que queremos que Nananana
aparezca al principio de catchphraseBuf
. El sourceStart
debería ser 7
ya que ese es el índice donde comienza Nananana
en wordsBuf
. El sourceEnd
seguiría siendo la longitud de los búferes.
En el indicador de REPL, copie el contenido de wordsBuf
de esta manera:
El REPL confirma que se han escrito 8
bytes. Note cómo se utiliza wordsBuf.length
como el valor para el parámetro sourceEnd
. Al igual que en los arreglos, la propiedad length
nos da el tamaño del búfer.
Ahora veamos el contenido de catchphraseBuf
:
El REPL devuelve:
Output'Nananana Turtle!'
¡Éxito! Pudimos modificar los datos de catchphraseBuf
copiando el contenido de wordsBuf
.
Puedes salir del REPL de Node.js si así lo deseas. Ten en cuenta que todas las variables que se crearon ya no estarán disponibles cuando lo hagas:
Conclusión
En este tutorial, aprendiste que los buffers son asignaciones de longitud fija en la memoria que almacenan datos binarios. Primero creaste buffers definiendo su tamaño en la memoria e inicializándolos con datos preexistentes. Luego, leíste datos de un buffer examinando sus bytes individuales y usando los métodos toString()
y toJSON()
. Finalmente, modificaste los datos almacenados por un buffer cambiando sus bytes individuales y usando los métodos write()
y copy()
.
Los buffers te brindan una gran comprensión de cómo se manipulan los datos binarios en Node.js. Ahora que puedes interactuar con los buffers, puedes observar las diferentes formas en que la codificación de caracteres afecta cómo se almacenan los datos. Por ejemplo, puedes crear buffers a partir de datos de cadena que no estén codificados en UTF-8 o ASCII y observar la diferencia en tamaño. También puedes tomar un buffer con UTF-8 y usar toString()
para convertirlo a otros esquemas de codificación.
Para aprender sobre buffers en Node.js, puedes leer la documentación de Node.js sobre el objeto Buffer
. Si deseas continuar aprendiendo Node.js, puedes regresar a la serie Cómo Programar en Node.js, o explorar proyectos de programación y configuraciones en nuestra página de temas de Node.
Source:
https://www.digitalocean.com/community/tutorials/using-buffers-in-node-js