Cómo agregar datos binarios a un búfer en node.js

81

Tengo un búfer con algunos datos binarios:

var b = new Buffer ([0x00, 0x01, 0x02]);

y quiero añadir 0x03.

¿Cómo puedo agregar más datos binarios? Estoy buscando en la documentación, pero para agregar datos debe ser una cadena, si no, se produce un error ( TypeError: Argument debe ser una cadena ):

var b = new Buffer (256);
b.write ("hola");
console.log (b.toString ("utf8", 0, 4)); //hola
b.write (", adios", 4);
console.log (b.toString ("utf8", 0, 11)); //hola, adios

Entonces, la única solución que puedo ver aquí es crear un nuevo búfer para cada dato binario agregado y copiarlo al búfer principal con el desplazamiento correcto:

var b = new Buffer (4); //4 for having a nice printed buffer, but the size will be 16KB
new Buffer ([0x00, 0x01, 0x02]).copy (b);
console.log (b); //<Buffer 00 01 02 00>
new Buffer ([0x03]).copy (b, 3);
console.log (b); //<Buffer 00 01 02 03>

Pero esto parece un poco ineficiente porque tengo que crear una instancia de un nuevo búfer para cada agregado.

¿Conoce una forma mejor de agregar datos binarios?

EDITAR

Escribí un BufferedWriter que escribe bytes en un archivo usando búferes internos. Igual que BufferedReader pero para escritura.

Un ejemplo rápido:

//The BufferedWriter truncates the file because append == false
new BufferedWriter ("file")
    .on ("error", function (error){
        console.log (error);
    })

    //From the beginning of the file:
    .write ([0x00, 0x01, 0x02], 0, 3) //Writes 0x00, 0x01, 0x02
    .write (new Buffer ([0x03, 0x04]), 1, 1) //Writes 0x04
    .write (0x05) //Writes 0x05
    .close (); //Closes the writer. A flush is implicitly done.

//The BufferedWriter appends content to the end of the file because append == true
new BufferedWriter ("file", true)
    .on ("error", function (error){
        console.log (error);
    })

    //From the end of the file:
    .write (0xFF) //Writes 0xFF
    .close (); //Closes the writer. A flush is implicitly done.

//The file contains: 0x00, 0x01, 0x02, 0x04, 0x05, 0xFF

ÚLTIMA ACTUALIZACIÓN

Utilice concat .

Gabriel Llamas
fuente
3
Sería más claro leer si las mini-respuestas en la parte superior fueran respuestas reales y la pregunta estuviera sola aquí.
Anko

Respuestas:

139

Respuesta actualizada para Node.js ~> 0.8

El nodo ahora puede concatenar búferes por sí solo.

var newBuffer = Buffer.concat([buffer1, buffer2]);

Respuesta anterior para Node.js ~ 0.6

Utilizo un módulo para agregar una .concatfunción, entre otras:

https://github.com/coolaj86/node-bufferjs

Sé que no es una solución "pura", pero funciona muy bien para mis propósitos.

Puntilla
fuente
La concatfunción hace exactamente lo que he publicado :(. Calcula la longitud total y luego copia los datos de todos los búferes ajustando el desplazamiento.
Gabriel Llamas
Así es como tiene que funcionar. Como señaló @stewe, los búferes se instancian a un tamaño fijo, debido a la forma en que se asigna la memoria.
Brad
2
Pero en c tenemos la función realloc para expandir dinámicamente la memoria cuando sea necesario. El node.js debería saber esto.
Gabriel Llamas
1
@GabrielLlamas, recomendaría enviar un parche a su repositorio.
Brad
11
Descubrí por qué node.js no tiene búferes dinámicos: markmail.org/message/vx2h3uslwgludu3y
Gabriel Llamas
10

Los búferes son siempre de tamaño fijo, no hay una forma incorporada de cambiar su tamaño de forma dinámica, por lo que su enfoque de copiarlos en un búfer más grande es la única forma.

Sin embargo, para ser más eficiente, puede hacer que el búfer sea más grande que el contenido original, por lo que contiene algo de espacio "libre" donde puede agregar datos sin reasignar el búfer. De esa manera, no es necesario crear un nuevo búfer y copiar el contenido en cada operación de adición.

Stewe
fuente
8

Esto es para ayudar a cualquiera que venga aquí en busca de una solución que quiera un enfoque puro. Recomendaría comprender este problema porque puede suceder en muchos lugares diferentes, no solo con un objeto JS Buffer. Al comprender por qué existe el problema y cómo resolverlo, mejorará su capacidad para resolver otros problemas en el futuro, ya que este es fundamental.

Para aquellos de nosotros que tenemos que lidiar con estos problemas en otros lenguajes, es bastante natural idear una solución, pero hay personas que pueden no darse cuenta de cómo abstraer las complejidades e implementar un búfer dinámico generalmente eficiente. El siguiente código puede tener potencial para optimizarse aún más.

Dejé el método de lectura sin implementar para mantener el tamaño del ejemplo pequeño.

La reallocfunción en C (o cualquier lenguaje que trate con asignaciones intrínsecas) no garantiza que la asignación se expandirá en tamaño sin mover los datos existentes, aunque a veces es posible. Por lo tanto, la mayoría de las aplicaciones cuando necesiten almacenar una cantidad desconocida de datos usarán un método como el siguiente y no se reasignarán constantemente, a menos que la reasignación sea muy poco frecuente. Básicamente, así es como la mayoría de los sistemas de archivos manejan la escritura de datos en un archivo. El sistema de archivos simplemente asigna otro nodo y mantiene todos los nodos vinculados entre sí, y cuando lee de él, la complejidad se abstrae para que el archivo / búfer parezca ser un solo búfer contiguo.

Para aquellos de ustedes que deseen comprender la dificultad de simplemente proporcionar un búfer dinámico de alto rendimiento, solo necesitan ver el código a continuación y también investigar un poco sobre los algoritmos del montón de memoria y cómo funciona el montón de memoria para los programas.

La mayoría de los lenguajes proporcionarán un búfer de tamaño fijo por razones de rendimiento y luego proporcionarán otra versión de tamaño dinámico. Algunos sistemas de lenguaje optan por un sistema de terceros en el que mantienen la funcionalidad principal mínima (distribución principal) y alientan a los desarrolladores a crear bibliotecas para resolver problemas de nivel adicional o superior. Es por eso que puede preguntarse por qué un idioma no proporciona alguna funcionalidad. Esta pequeña funcionalidad básica permite reducir los costos de mantenimiento y mejora del lenguaje, sin embargo, termina teniendo que escribir sus propias implementaciones o dependiendo de un tercero.

var Buffer_A1 = function (chunk_size) {
    this.buffer_list = [];
    this.total_size = 0;
    this.cur_size = 0;
    this.cur_buffer = [];
    this.chunk_size = chunk_size || 4096;

    this.buffer_list.push(new Buffer(this.chunk_size));
};

Buffer_A1.prototype.writeByteArrayLimited = function (data, offset, length) {
    var can_write = length > (this.chunk_size - this.cur_size) ? (this.chunk_size - this.cur_size) : length;

    var lastbuf = this.buffer_list.length - 1;

    for (var x = 0; x < can_write; ++x) {
        this.buffer_list[lastbuf][this.cur_size + x] = data[x + offset];
    }

    this.cur_size += can_write;
    this.total_size += can_write;

    if (this.cur_size == this.chunk_size) {
        this.buffer_list.push(new Buffer(this.chunk_size));
        this.cur_size = 0;
    }

    return can_write;
};

/*
    The `data` parameter can be anything that is array like. It just must
    support indexing and a length and produce an acceptable value to be
    used with Buffer.
*/
Buffer_A1.prototype.writeByteArray = function (data, offset, length) {
    offset = offset == undefined ? 0 : offset;
    length = length == undefined ? data.length : length;

    var rem = length;
    while (rem > 0) {
        rem -= this.writeByteArrayLimited(data, length - rem, rem);
    }
};

Buffer_A1.prototype.readByteArray = function (data, offset, length) {
    /*
        If you really wanted to implement some read functionality
        then you would have to deal with unaligned reads which could
        span two buffers.
    */
};

Buffer_A1.prototype.getSingleBuffer = function () {
    var obuf = new Buffer(this.total_size);
    var cur_off = 0;
    var x;

    for (x = 0; x < this.buffer_list.length - 1; ++x) {
        this.buffer_list[x].copy(obuf, cur_off);
        cur_off += this.buffer_list[x].length;
    }

    this.buffer_list[x].copy(obuf, cur_off, 0, this.cur_size);

    return obuf;
};
kmcguire
fuente
Aconsejaría extrema precaución al usar esta solución. Si la razón por la que desea búferes de tamaño variable es el rendimiento, no lo use . Cada byte escrito en la matriz de tamaño variable incurre this.buffer_list[lastbuf][this.cur_size + x] = data[x + offset];, lo que introduce innecesariamente una búsqueda de hash adicional, muchas verificaciones de matriz adicionales y dos verificaciones de enteros SMI con cada byte. Si lo que desea es rendimiento, le recomiendo encarecidamente que no utilice esta respuesta. En su lugar, asigne una nueva matriz del tamaño deseado y copie los datos en la nueva matriz. Eso es lo que hace Java y es realmente rápido.
Jack Giffin
0

insertar byte en un lugar específico.

insertToArray(arr,index,item) {
   return Buffer.concat([arr.slice(0,index),Buffer.from(item,"utf-8"),arr.slice(index)]);
}
Havrylov Anton
fuente