Un Buffer es la respuesta de Node a un problema para el que JavaScript originalmente no tenía una solución: ¿cómo se mantienen y manipulan datos binarios crudos (contenido de archivos, paquetes de red, bytes de imágenes) en un lenguaje cuyo único tipo numérico era un flotante de 64 bits inadecuado para el trabajo a nivel de byte?
Conceptos básicos de Buffers muestra la API diaria para asignar y segmentar esos datos, y Codificaciones de texto cubre el caso específico de convertir bytes a y desde cadenas. Esta página se encuentra debajo de ambas: qué es realmente un Buffer en la memoria, cómo se relaciona con la familia estándar TypedArray, y por qué "datos binarios" y "texto" son dos preocupaciones separadas que solo se encuentran en un paso de conversión explícito.
Un Buffer es una vista de longitud fija sobre bytes crudos almacenados fuera del heap recolectado por el recolector de basura de V8, expuesto a través de una API que es un superconjunto del Uint8Array estándar.
Por qué es importante: Los datos binarios (sockets de red, archivos, criptografía) no encajan en un lenguaje construido alrededor de valores dinámicos y recolectados por el recolector de basura. Buffer le da a Node un almacenamiento predecible y de bajo costo para exactamente esos datos.
Conceptos clave:memoria cruda, ArrayBuffer, TypedArray, vista de bytes, codificación, agrupación de buffers.
Cuándo usarlo: Leer/escribir archivos o sockets a nivel de byte, implementar un protocolo binario, aplicar hash o cifrar datos, o convertir entre texto y bytes en un límite del sistema.
Limitaciones / Compromisos: Los Buffers tienen un tamaño fijo una vez asignados, viven fuera del heap que el recolector de basura de V8 optimiza, y la ruta de asignación más rápida (allocUnsafe) puede exponer contenido de memoria antiguo si no tienes cuidado.
Temas relacionados: TypedArrays y ArrayBuffer, codificación de texto (UTF-8), streams, diseño de protocolos binarios.
Antes de que existiera Buffer, JavaScript no tenía una buena manera de representar "dame exactamente estos 512 bytes, y déjame leer o escribir cualquiera de ellos directamente". Los números eran flotantes, las cadenas eran secuencias UTF-16, y ninguno se mapea limpiamente a un flujo de bytes de un socket TCP o un archivo en disco.
Node introdujo Buffer en sus primeras versiones para llenar ese vacío: un array de enteros de longitud fija, cada uno limitado a un solo byte (0-255), respaldado por memoria asignada fuera del heap normal de JavaScript. Ese detalle de "fuera del heap" importa más de lo que parece a primera vista: significa que las grandes cargas binarias no ejercen presión sobre el recolector de basura de V8 de la misma manera que lo haría un array equivalente de números de JavaScript, y significa que la memoria tiene un diseño crudo, similar a C, que se puede entregar directamente a las llamadas al sistema.
Una analogía útil: piensa en un Buffer como una fila numerada de taquillas de almacenamiento, cada una conteniendo exactamente un byte, con un recuento total fijo decidido en el momento en que se construye la fila. Puedes leer o sobrescribir cualquier taquilla por su número, puedes entregar un segmento de taquillas consecutivas a otra cosa sin copiar su contenido, pero no puedes añadir una taquilla a la fila ni quitar una; la longitud de la fila es fija durante toda su vida útil.
import { Buffer } from 'node:buffer';const buf = Buffer.alloc(4); // 4 bytes rellenados con ceros, fuera del heap de V8buf[0] = 0xff; // escribe un byte directamenteconsole.log(buf); // <Buffer ff 00 00 00>
Cuando JavaScript estandarizó más tarde sus propios tipos de datos binarios —ArrayBuffer y la familia TypedArray (Uint8Array, Int32Array, etc.)— Node adaptó Buffer para que se apoyara en ellos en lugar de mantener una implementación completamente separada.
Hoy en día, un Buffer es una subclase de Uint8Array, que a su vez es una vista sobre un ArrayBuffer de nivel inferior. Esa estratificación es el hecho mecánico clave en el que se basa toda esta sección: el ArrayBuffer es el bloque real de memoria cruda, y un TypedArray (incluido Buffer) es una lente que interpreta parte o la totalidad de esa memoria como una secuencia de valores del mismo tamaño, en el caso de Buffer, siempre bytes individuales.
Debido a que Buffer es un Uint8Array bajo el capó, cada método TypedArray estándar funciona en él, y un Buffer puede compartir el mismo ArrayBuffer subyacente que un Uint8Array creado en otro lugar; mutar uno a través de su vista puede ser visible a través del otro, ya que son ventanas a la misma memoria en lugar de copias independientes. El Buffer de Node añade comodidad adicional: conversión de cadenas con reconocimiento de codificación, ayudantes de lectura/escritura de enteros en desplazamientos de bytes arbitrarios (readUInt32BE, writeInt16LE y similares), y constructores ajustados para los propios casos de uso de Node.
La codificación es el segundo mecanismo que vale la pena separar claramente del modelo de memoria: un Buffer nunca "es" UTF-8 o cualquier otra codificación, son solo bytes. La codificación es una regla aplicada en el momento de la conversión, que le dice a Node cómo interpretar esos bytes como texto (o viceversa) cuando lo solicitas explícitamente.
const buf = Buffer.from('café', 'utf8'); // 5 bytes - 'é' ocupa 2 bytes en UTF-8console.log(buf.length); // 5, no 4console.log(buf.toString('utf8')); // 'café' - solo es correcto con una codificación coincidente
Esa distinción explica una sorpresa común: buf.length cuenta bytes, no caracteres, y leer una cadena codificada con múltiples bytes con el argumento de codificación incorrecto produce texto silenciosamente incorrecto en lugar de un error, porque Buffer no tiene forma de saber qué codificación pretendías.
La estrategia de asignación es el tercer mecanismo, y es una compensación directa de seguridad de la memoria. Buffer.alloc(n) rellena con ceros su memoria antes de devolverla, garantizando que no se filtren datos antiguos. Buffer.allocUnsafe(n) omite ese relleno con ceros y, en su lugar, recicla memoria de un pool interno preasignado para mayor velocidad, lo que significa que un Buffer inseguro recién asignado puede contener brevemente los bytes que estaban previamente en esa ranura del pool, hasta que los sobrescribas tú mismo. Seguridad de Buffer cubre exactamente cuándo esa compensación es (y no es) aceptable.
La relación entre Buffer, TypedArray y las API binarias estándar de la Web (TextEncoder, TextDecoder, Blob) ha convergido significativamente a medida que Node ha adoptado más de la biblioteca estándar del navegador junto con la suya propia específica de Node, razón por la cual el código Node moderno mezcla cada vez más ambos libremente en lugar de tratar a Buffer como un concepto aislado y exclusivo de Node.
Enfoque
Fortaleza
Debilidad
Mejor ajuste
Buffer
Métodos de conveniencia nativos de Node (readUInt32BE, toString con reconocimiento de codificación); cero dependencia extra
Superficie de API específica de Node; históricamente algunos errores comunes (asignación insegura)
E/S de archivos, sockets, código de protocolo binario solo para Node
Uint8Array / ArrayBuffer simple
Estándar web, portable a navegadores y otros entornos de ejecución de JS sin cambios
Menos métodos de conveniencia incorporados para la manipulación a nivel de byte
Código destinado a ejecutarse tanto en Node como en el navegador
TextEncoder / TextDecoder
Estándar web, manejo explícito y predecible de UTF-8 con modos de error
Solo texto - no es un contenedor general de datos binarios
Conversión estricta entre texto UTF-8 y bytes
A escala, la propiedad de "memoria fuera del heap" se convierte en una preocupación operativa en lugar de solo una nota al pie de página de rendimiento: un gran número de Buffers de larga duración reduce la presión sobre el recolector de basura de V8, pero aún consumen memoria del proceso que las herramientas de monitoreo deben contabilizar por separado de las métricas típicas del heap. Esa es una de las razones por las que cargar un archivo grande completo en un Buffer gigante suele ser una decisión incorrecta: Conceptos básicos de Streams cubre el procesamiento de los mismos datos en fragmentos delimitados en lugar de mantenerlos todos en memoria a la vez.
El código sensible a la seguridad tiene su propio filo aquí: debido a que allocUnsafe recicla la memoria del pool, el código que asigna un Buffer inseguro y solo lo llena parcialmente antes de enviarlo a algún lugar (un socket, un cuerpo de respuesta) puede filtrar fragmentos de datos previos no relacionados. Seguridad de Buffer cubre esto y preocupaciones relacionadas como la comparación segura en el tiempo para datos de bytes criptográficos. Protocolos Binarios se basa en la mecánica de lectura/escritura en desplazamiento descrita anteriormente para analizar y construir formatos de cable reales.
"Un Buffer almacena texto con una codificación adjunta." Un Buffer solo almacena bytes; la codificación es una regla de conversión aplicada cuando conviertes explícitamente a o desde una cadena, no una propiedad que los bytes llevan consigo.
"buf.length te da el recuento de caracteres del texto que contiene." Te da el recuento de bytes; para cualquier codificación que use más de un byte por carácter (los caracteres no ASCII de UTF-8, por ejemplo), esos números divergen.
"Buffer y Uint8Array son APIs no relacionadas y competidoras." Buffer es una subclase de Uint8Array: los mismos bytes subyacentes, con métodos de conveniencia adicionales específicos de Node superpuestos, no un tipo de datos separado.
"Buffer.allocUnsafe es solo una versión más rápida y equivalente de Buffer.alloc." Omite por completo el relleno con ceros, por lo que la memoria devuelta puede contener brevemente datos sobrantes de usos anteriores; es más rápido específicamente porque sacrifica esa garantía, no una mejora gratuita.
"Segmentar un Buffer copia sus datos."buf.subarray() (y el obsoleto buf.slice()) devuelven una vista sobre la misma memoria subyacente por defecto; escribir a través del segmento también puede mutar los bytes del Buffer original.
¿Qué es exactamente un Buffer, a nivel de memoria?
Una secuencia de longitud fija de valores de un solo byte respaldada por memoria asignada fuera del heap de JavaScript recolectado por el recolector de basura de V8, expuesta a tu código a través de una API que es un superconjunto del Uint8Array estándar.
¿Por qué Node no usó simplemente arrays de JavaScript regulares para datos binarios?
Los arrays regulares contienen valores JavaScript arbitrarios (flotantes por defecto) con longitud dinámica y memoria gestionada por el heap; nada de eso se adapta de manera eficiente a datos binarios crudos, de tamaño fijo y restringidos a bytes, por lo que Node necesitaba un tipo diseñado específicamente antes de que JavaScript tuviera sus propios TypedArrays.
¿Cómo se relaciona Buffer con `ArrayBuffer` y `TypedArray`?
ArrayBuffer es el bloque de memoria cruda; un TypedArray (como Uint8Array) es una vista tipada sobre parte o la totalidad de esa memoria; Buffer es la propia subclase de Uint8Array de Node, que añade métodos de conveniencia mientras sigue siendo totalmente compatible con la maquinaria estándar de TypedArray.
¿Un Buffer sabe qué codificación de texto contiene?
No, un Buffer son solo bytes sin metadatos adjuntos sobre la codificación; tú proporcionas la codificación explícitamente cada vez que conviertes a o desde una cadena (buf.toString('utf8'), Buffer.from(str, 'utf8')), y Node confía en la codificación que nombres.
¿Por qué `buf.length` a veces es diferente del recuento de caracteres de la cadena?
Porque buf.length cuenta bytes, y las codificaciones como UTF-8 usan un número variable de bytes por carácter; los caracteres ASCII ocupan un byte, pero muchos caracteres no ASCII ocupan dos, tres o cuatro, por lo que el recuento de bytes y el recuento de caracteres solo coinciden para contenido puramente ASCII.
¿Cuál es la diferencia real entre `Buffer.alloc` y `Buffer.allocUnsafe`?
Buffer.alloc(n) rellena la memoria con ceros antes de devolverla, garantizando bytes limpios a un pequeño costo de rendimiento; Buffer.allocUnsafe(n) omite ese paso y recicla memoria de un pool interno para mayor velocidad, lo que significa que los bytes devueltos pueden contener brevemente datos sobrantes no relacionados hasta que los sobrescribas.
¿Es costoso segmentar un Buffer?
No, y es precisamente porque es barato que puede sorprenderte: subarray()/slice() devuelven una vista sobre la misma memoria en lugar de copiarla, por lo que la operación en sí es rápida, pero las escrituras a través del segmento también afectan al Buffer original.
¿Debo cargar un archivo completo en un Buffer, o usar un stream?
Para archivos pequeños y delimitados, un solo Buffer es simple y está bien; para entradas grandes o ilimitadas, un stream procesa los datos en fragmentos de tamaño fijo en lugar de mantener toda la carga útil en memoria a la vez, lo que mantiene el uso de memoria predecible independientemente del tamaño de la entrada.
¿Buffer y los `TextEncoder`/`TextDecoder` estándar de la Web hacen el mismo trabajo?
Se superponen específicamente para el caso de conversión de texto: TextEncoder/TextDecoder manejan la conversión de texto a bytes UTF-8 de una manera estándar y portable para la Web, mientras que Buffer es un contenedor binario de propósito general con una API mucho más amplia que solo texto.
¿Puede la mutación de un `Uint8Array` afectar a un Buffer, o viceversa?
Sí, si comparten el mismo ArrayBuffer subyacente; dado que Buffer es una subclase de Uint8Array y ambos son solo vistas sobre la memoria, dos vistas sobre el mismo bloque ven las escrituras del otro, no son copias independientes.
¿Por qué Node mantiene un pool de memoria para la asignación de Buffer?
Asignar pequeños buffers uno a la vez desde el sistema operativo tiene una sobrecarga real por asignación; la agrupación pre-reserva un bloque más grande y distribuye segmentos de él para pequeñas asignaciones, lo que es más rápido pero es exactamente el mecanismo que hace posible el riesgo de memoria obsoleta de allocUnsafe.