Convertir un búfer binario NodeJS a JavaScript ArrayBuffer

133

¿Cómo puedo convertir un búfer binario NodeJS en un ArrayBuffer de JavaScript?

Drake Amara
fuente
1
Tengo curiosidad por saber por qué necesitarías hacer esto.
Chris Biscardi
14
Un buen ejemplo sería escribir una biblioteca que funcionara con archivos en navegadores y también para archivos NodeJS.
fbstj
1
o usando una biblioteca de navegador en NodeJS
OrangeDog
1
Otra razón es que un flotante toma demasiados bytes de RAM cuando se almacena en un Array. Por lo tanto, para almacenar muchos flotadores, necesita Float32Array4 bytes. Y si desea una serialización rápida de esos flotadores en un archivo, necesita un Buffer, ya que la serialización a JSON lleva años.
nponeccop
Quiero saber exactamente lo mismo para enviar datos genéricos usando WebRTC y es increíble que tantas respuestas aquí tengan tantos me gusta, pero no responda la pregunta real ...
Felix Crazzolara

Respuestas:

134

Las instancias de Buffertambién son instancias deUint8Array en node.js 4.xy superior. Por lo tanto, la solución más eficiente es acceder a la buf.bufferpropiedad directamente, según https://stackoverflow.com/a/31394257/1375574 . El constructor Buffer también toma un argumento ArrayBufferView si necesita ir en la otra dirección.

Tenga en cuenta que esto no creará una copia, lo que significa que las escrituras en cualquier ArrayBufferView se escribirán en la instancia original de Buffer.


En versiones anteriores, node.js tiene ArrayBuffer como parte de v8, pero la clase Buffer proporciona una API más flexible. Para leer o escribir en un ArrayBuffer, solo necesita crear una vista y copiarla.

De Buffer a ArrayBuffer:

function toArrayBuffer(buf) {
    var ab = new ArrayBuffer(buf.length);
    var view = new Uint8Array(ab);
    for (var i = 0; i < buf.length; ++i) {
        view[i] = buf[i];
    }
    return ab;
}

De ArrayBuffer a Buffer:

function toBuffer(ab) {
    var buf = Buffer.alloc(ab.byteLength);
    var view = new Uint8Array(ab);
    for (var i = 0; i < buf.length; ++i) {
        buf[i] = view[i];
    }
    return buf;
}
Martin Thomson
fuente
55
También te recomiendo que optimices esto copiando enteros cuando sea posible usando DataView. Hasta size&0xfffffffe, copie enteros de 32 bits, luego, si queda 1 byte, copie un entero de 8 bits, si tiene 2 bytes, copie un entero de 16 bits, y si tiene 3 bytes, copie un entero de 16 bits y 8 bits.
Triang3l
3
Vea la respuesta de kraag22 para una implementación más simple de la mitad de esto.
OrangeDog
He probado Buffer -> ArrayBuffer con un módulo destinado al uso del navegador y funciona de manera brillante. ¡Gracias!
pospi
3
¿Por qué se abdevuelve? No hay nada hecho con ab? Siempre consigo {}como resultado.
Andi Giga
1
'El slice()método devuelve un nuevo ArrayBuffercuyo contenido es una copia de ArrayBufferlos bytes de este desde inicio, inclusivo, hasta el final, exclusivo' - MDNArrayBuffer.prototype.slice()
Константин Ван
61

Sin dependencias, más rápido, Node.js 4.xy posterior

Buffers son Uint8Arrays, por lo que solo necesita cortar (copiar) su región del respaldo ArrayBuffer.

// Original Buffer
let b = Buffer.alloc(512);
// Slice (copy) its segment of the underlying ArrayBuffer
let ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);

El material slicey el desplazamiento son necesarios porque los pequeños Buffers (menos de 4 kB por defecto, la mitad del tamaño del grupo ) pueden ser vistas en un espacio compartido ArrayBuffer. Sin segmentar, puede terminar con ArrayBufferdatos que contienen de otro Buffer. Ver explicación en los documentos .

Si finalmente necesita un TypedArray, puede crear uno sin copiar los datos:

// Create a new view of the ArrayBuffer without copying
let ui32 = new Uint32Array(b.buffer, b.byteOffset, b.byteLength / Uint32Array.BYTES_PER_ELEMENT);

Sin dependencias, velocidad moderada, cualquier versión de Node.js

Use la respuesta de Martin Thomson , que se ejecuta en O (n) . (Consulte también mis respuestas a los comentarios sobre su respuesta sobre las no optimizaciones. El uso de un DataView es lento. Incluso si necesita voltear bytes, hay formas más rápidas de hacerlo).

Dependencia, rápida, Node.js ≤ 0.12 o iojs 3.x

Puede usar https://www.npmjs.com/package/memcpy para ir en cualquier dirección (Buffer to ArrayBuffer y viceversa). Es más rápido que las otras respuestas publicadas aquí y es una biblioteca bien escrita. Los nodos 0.12 a iojs 3.x requieren la bifurcación de ngossen (vea esto ).

ZachB
fuente
No vuelve a compilar nodo> 0,12
Pawel Veselov
1
Use la bifurcación de ngossen : github.com/dcodeIO/node-memcpy/pull/6 . Vea también mi nueva respuesta si está utilizando el nodo 4+.
ZachB
¿Dónde estaban .byteLengthy .byteOffsetdocumentados?
Константин Ван
1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);me salvó el día
Alexey Sh.
56

"De ArrayBuffer a Buffer" podría hacerse de esta manera:

var buffer = Buffer.from( new Uint8Array(ab) );
kraag22
fuente
27
Eso es lo contrario de lo que OP quería.
Alexander Gonchiy
43
Pero eso es lo que quería buscar en Google mi problema y me alegro de haber encontrado la solución.
Maciej Krawczyk
27

Una forma más rápida de escribirlo.

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

Sin embargo, esto parece ejecutarse aproximadamente 4 veces más lento que la función toArrayBuffer sugerida en un búfer con 1024 elementos.

David Fooks
fuente
3
Adición tardía: @trevnorris dice "a partir de [V8] 4.3 Los buffers están respaldados por Uint8Array", por lo que posiblemente esto sea más rápido ahora ...
ChrisV
Vea mi respuesta para la forma segura de hacer esto.
ZachB
3
Lo probé con v5.6.0 y fue el más rápido
daksh_019
1
Esto solo funciona porque las instancias de Buffertambién son instancias de Uint8ArrayNode.js 4.xy superior. Para versiones inferiores de Node.js, debe implementar una toArrayBufferfunción.
Benny Neugebauer
14

1. A Bufferes solo una vista para mirar un ArrayBuffer.

A Buffer, de hecho, es un FastBuffer, que extends(hereda de) Uint8Array, que es una vista de unidad de octeto ("acceso parcial") de la memoria real, unArrayBuffer .

  📜 Node.js 9.4.0/lib/buffer.js#L65-L73
class FastBuffer extends Uint8Array {
  constructor(arg1, arg2, arg3) {
    super(arg1, arg2, arg3);
  }
}
FastBuffer.prototype.constructor = Buffer;
internalBuffer.FastBuffer = FastBuffer;

Buffer.prototype = FastBuffer.prototype;

2. El tamaño de an ArrayBuffery el tamaño de su vista pueden variar.

Razón # 1: Buffer.from(arrayBuffer[, byteOffset[, length]]) .

Con Buffer.from(arrayBuffer[, byteOffset[, length]]), puede crear un Buffercon especificar su subyacente ArrayBuffery la posición y el tamaño de la vista.

const test_buffer = Buffer.from(new ArrayBuffer(50), 40, 10);
console.info(test_buffer.buffer.byteLength); // 50; the size of the memory.
console.info(test_buffer.length); // 10; the size of the view.

Razón # 2: FastBufferasignación de memoria de.

Asigna la memoria de dos formas diferentes según el tamaño.

  • Si el tamaño es menor que la mitad del tamaño de un grupo de memoria y no es 0 ("pequeño") : utiliza un grupo de memoria para preparar la memoria requerida.
  • De lo contrario : crea un dedicado ArrayBufferque se ajusta exactamente a la memoria requerida.
  📜 Node.js 9.4.0/lib/buffer.js#L306-L320
function allocate(size) {
  if (size <= 0) {
    return new FastBuffer();
  }
  if (size < (Buffer.poolSize >>> 1)) {
    if (size > (poolSize - poolOffset))
      createPool();
    var b = new FastBuffer(allocPool, poolOffset, size);
    poolOffset += size;
    alignPool();
    return b;
  } else {
    return createUnsafeBuffer(size);
  }
}
  📜 Node.js 9.4.0/lib/buffer.js#L98-L100
function createUnsafeBuffer(size) {
  return new FastBuffer(createUnsafeArrayBuffer(size));
}

¿Qué quiere decir con un " grupo de memoria "?

Un grupo de memoria es un bloque de memoria preasignado de tamaño fijo para mantener fragmentos de memoria de tamaño pequeño durante Buffers. Su uso mantiene los fragmentos de memoria de pequeño tamaño muy juntos, por lo que evita la fragmentación. causada por la administración separada (asignación y desasignación) de fragmentos de memoria de pequeño tamaño.

En este caso, las agrupaciones de memoria son ArrayBuffers cuyo tamaño es de 8 KiB de forma predeterminada, que se especifica en Buffer.poolSize. Cuando se trata de proporcionar un fragmento de memoria de tamaño pequeño para un Buffer, comprueba si el último grupo de memoria tiene suficiente memoria disponible para manejar esto; si es así, crea un Bufferque "ve" el fragmento parcial dado del grupo de memoria, de lo contrario, crea un nuevo grupo de memoria y así sucesivamente.


Puede acceder al subyacente ArrayBufferde a Buffer. El Buffer's bufferde propiedad (es decir, heredado de Uint8Array) la sostiene. Un “pequeño” Buffer 's bufferestablecimiento es el ArrayBufferque representa la totalidad del bloque de memoria. Entonces, en este caso, el ArrayBuffery el Buffervaría en tamaño.

const zero_sized_buffer = Buffer.allocUnsafe(0);
const small_buffer = Buffer.from([0xC0, 0xFF, 0xEE]);
const big_buffer = Buffer.allocUnsafe(Buffer.poolSize >>> 1);

// A `Buffer`'s `length` property holds the size, in octets, of the view.
// An `ArrayBuffer`'s `byteLength` property holds the size, in octets, of its data.

console.info(zero_sized_buffer.length); /// 0; the view's size.
console.info(zero_sized_buffer.buffer.byteLength); /// 0; the memory..'s size.
console.info(Buffer.poolSize); /// 8192; a memory pool's size.

console.info(small_buffer.length); /// 3; the view's size.
console.info(small_buffer.buffer.byteLength); /// 8192; the memory pool's size.
console.info(Buffer.poolSize); /// 8192; a memory pool's size.

console.info(big_buffer.length); /// 4096; the view's size.
console.info(big_buffer.buffer.byteLength); /// 4096; the memory's size.
console.info(Buffer.poolSize); /// 8192; a memory pool's size.

3. Entonces necesitamos extraer la memoria que " ve ".

Un ArrayBuffertamaño es fijo, por lo que debemos extraerlo haciendo una copia de la pieza. Para hacer esto, utilizamos Bufferla byteOffsetpropiedad y la lengthpropiedad , que se heredan Uint8Array, y el ArrayBuffer.prototype.slicemétodo , que hace una copia de una parte de un ArrayBuffer. El slice()método -ing aquí fue inspirado por @ZachB .

const test_buffer = Buffer.from(new ArrayBuffer(10));
const zero_sized_buffer = Buffer.allocUnsafe(0);
const small_buffer = Buffer.from([0xC0, 0xFF, 0xEE]);
const big_buffer = Buffer.allocUnsafe(Buffer.poolSize >>> 1);

function extract_arraybuffer(buf)
{
    // You may use the `byteLength` property instead of the `length` one.
    return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length);
}

// A copy -
const test_arraybuffer = extract_arraybuffer(test_buffer); // of the memory.
const zero_sized_arraybuffer = extract_arraybuffer(zero_sized_buffer); // of the... void.
const small_arraybuffer = extract_arraybuffer(small_buffer); // of the part of the memory.
const big_arraybuffer = extract_arraybuffer(big_buffer); // of the memory.

console.info(test_arraybuffer.byteLength); // 10
console.info(zero_sized_arraybuffer.byteLength); // 0
console.info(small_arraybuffer.byteLength); // 3
console.info(big_arraybuffer.byteLength); // 4096

4. Mejora del rendimiento

Si va a utilizar los resultados como de solo lectura, o está bien modificar el Buffercontenido de la entrada , puede evitar la copia innecesaria de la memoria.

const test_buffer = Buffer.from(new ArrayBuffer(10));
const zero_sized_buffer = Buffer.allocUnsafe(0);
const small_buffer = Buffer.from([0xC0, 0xFF, 0xEE]);
const big_buffer = Buffer.allocUnsafe(Buffer.poolSize >>> 1);

function obtain_arraybuffer(buf)
{
    if(buf.length === buf.buffer.byteLength)
    {
        return buf.buffer;
    } // else:
    // You may use the `byteLength` property instead of the `length` one.
    return buf.subarray(0, buf.length);
}

// Its underlying `ArrayBuffer`.
const test_arraybuffer = obtain_arraybuffer(test_buffer);
// Just a zero-sized `ArrayBuffer`.
const zero_sized_arraybuffer = obtain_arraybuffer(zero_sized_buffer);
// A copy of the part of the memory.
const small_arraybuffer = obtain_arraybuffer(small_buffer);
// Its underlying `ArrayBuffer`.
const big_arraybuffer = obtain_arraybuffer(big_buffer);

console.info(test_arraybuffer.byteLength); // 10
console.info(zero_sized_arraybuffer.byteLength); // 0
console.info(small_arraybuffer.byteLength); // 3
console.info(big_arraybuffer.byteLength); // 4096
Константин Ван
fuente
44
Todo esto está muy bien ... pero ¿realmente respondiste la pregunta de OP? Si lo hiciste, está enterrado ...
Tustin2121
¡Gran respuesta! En obtain_arraybuffer: buf.buffer.subarrayno parece existir. ¿Quisiste decir buf.buffer.sliceaquí?
todos los días productivo el
@everydayproductive Gracias. Como puede ver en el historial de edición, en realidad lo usé ArrayBuffer.prototype.slicey luego lo modifiqué Uint8Array.prototype.subarray. Ah, y lo hice mal. Probablemente se confundió un poco en ese entonces. Todo está bien ahora gracias a ti.
Константин Ван
12

Utiliza el siguiente paquete excelente NPM: to-arraybuffer.

O bien, puede implementarlo usted mismo. Si se llama a su búfer buf, haga esto:

buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
Feross
fuente
21
Joyent lo sacó
aleclarson
1

Puedes pensar en un ArrayBuffercomo mecanografiado Buffer.

Una ArrayBuffer, por tanto, siempre tiene un tipo (el denominado "Matriz de búfer Ver"). Normalmente, la Vista de búfer de matriz tiene un tipo de Uint8Arrayo Uint16Array.

Hay un buen artículo de Renato Mangini sobre la conversión entre un ArrayBuffer y una Cadena .

He resumido las partes esenciales en un ejemplo de código (para Node.js). También muestra cómo convertir entre los mecanografiados ArrayBuffery los no tipificados Buffer.

function stringToArrayBuffer(string) {
  const arrayBuffer = new ArrayBuffer(string.length);
  const arrayBufferView = new Uint8Array(arrayBuffer);
  for (let i = 0; i < string.length; i++) {
    arrayBufferView[i] = string.charCodeAt(i);
  }
  return arrayBuffer;
}

function arrayBufferToString(buffer) {
  return String.fromCharCode.apply(null, new Uint8Array(buffer));
}

const helloWorld = stringToArrayBuffer('Hello, World!'); // "ArrayBuffer" (Uint8Array)
const encodedString = new Buffer(helloWorld).toString('base64'); // "string"
const decodedBuffer = Buffer.from(encodedString, 'base64'); // "Buffer"
const decodedArrayBuffer = new Uint8Array(decodedBuffer).buffer; // "ArrayBuffer" (Uint8Array)

console.log(arrayBufferToString(decodedArrayBuffer)); // prints "Hello, World!"
Benny Neugebauer
fuente
0

Intenté lo anterior para un Float64Array y simplemente no funcionó.

Terminé dándome cuenta de que realmente los datos debían leerse 'EN' la vista en fragmentos correctos. Esto significa leer 8 bytes a la vez desde el Buffer de origen.

De todos modos esto es con lo que terminé ...

var buff = new Buffer("40100000000000004014000000000000", "hex");
var ab = new ArrayBuffer(buff.length);
var view = new Float64Array(ab);

var viewIndex = 0;
for (var bufferIndex=0;bufferIndex<buff.length;bufferIndex=bufferIndex+8)            {

    view[viewIndex] = buff.readDoubleLE(bufferIndex);
    viewIndex++;
}
Exitos
fuente
Es por eso que la respuesta de Martin Thomson usa Uint8Array: es independiente del tamaño de los elementos. Los Buffer.read*métodos también son lentos.
ZachB
Múltiples vistas de matriz con tipo pueden hacer referencia al mismo ArrayBuffer utilizando la misma memoria. Cada valor en un Buffer es un byte, por lo que debe colocarlo en una matriz con un tamaño de elemento de 1 byte. Puede usar el método de Martin, luego hacer un nuevo Float64Array usando el mismo arraybuffer en el constructor.
ZachB
0

Este proxy expondrá el búfer como cualquiera de los TypedArrays, sin ninguna copia. :

https://www.npmjs.com/package/node-buffer-as-typedarray

Solo funciona en LE, pero se puede portar fácilmente a BE. Además, nunca pude probar qué tan eficiente es esto.

Dlabz
fuente
1
Si bien este enlace puede responder la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden volverse inválidas si la página vinculada cambia
koceeng
2
Puede que mi redacción no suene muy oficial, pero proporciona suficiente información para recrear la solución. La solución se basa en JavaScript Proxy Object para envolver un NodeJS Buffer nativo con getters y setters utilizados por TypedArrays. Esto hace que la instancia de Buffer sea compatible con cualquier biblioteca que requiera la interfaz Typed Array. Esta es la respuesta que esperaba el póster original, pero no dude en descartarlo, ya que no se ajusta a su jerga académica / corporativa. Mira si me importa.
Dlabz
-1

NodeJS, en un momento (creo que fue v0.6.x) tenía soporte para ArrayBuffer. Creé una pequeña biblioteca para la codificación y decodificación base64 aquí , pero desde la actualización a v0.7, las pruebas (en NodeJS) fallan. Estoy pensando en crear algo que normalice esto, pero hasta entonces, supongo que se Bufferdebe usar el nativo de Node .

arunjitsingh
fuente
-6

Ya actualicé mi nodo a la Versión 5.0.0 y trabajo con esto:

function toArrayBuffer(buffer){
    var array = [];
    var json = buffer.toJSON();
    var list = json.data

    for(var key in list){
        array.push(fixcode(list[key].toString(16)))
    }

    function fixcode(key){
        if(key.length==1){
            return '0'+key.toUpperCase()
        }else{
            return key.toUpperCase()
        }
    }

    return array
}

Lo uso para verificar la imagen de mi disco vhd.

Miguel Valentine
fuente
Esto parece un método especializado (y lento) basado en serialización, no un método genérico para convertir a / desde Buffer / ArrayBuffer?
ZachB
@ZachB es un método genérico para V5.0.0 + [solo] = =.
Miguel Valentine
toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- esto devuelve una matriz de cadenas, no enteros / bytes.
ZachB
@ZachB return array -> lista de retorno. Arreglo int-> string para stdout
Miguel Valentine
En ese caso, es lo mismo que stackoverflow.com/a/19544002/1218408 , y aún sin necesidad de las comprobaciones de desplazamiento de bytes en stackoverflow.com/a/31394257/1218408 .
ZachB