Convertire un buffer binario NodeJS in JavaScript ArrayBuffer


133

Come posso convertire un buffer binario NodeJS in un ArrayBuffer JavaScript?


1
Sono curioso di sapere perché dovresti farlo?
Chris Biscardi,

14
un buon esempio sarebbe scrivere una libreria che ha funzionato con i file nei browser e anche per i file NodeJS?
fbstj,

1
o utilizzando una libreria di browser in NodeJS
OrangeDog

1
Un altro motivo è che un float richiede troppi byte di RAM quando viene archiviato in un Array. Quindi per memorizzare molti float è necessario Float32Arraydove ci vogliono 4 byte. E se si desidera una rapida serializzazione di quei float su un file, è necessario un Buffer, poiché la serializzazione su JSON richiede secoli.
nponeccop,

Voglio sapere esattamente la stessa cosa per inviare dati generici utilizzando WebRTC ed è incredibile che così tante risposte qui abbiano così tanti Mi piace, ma non rispondere alla domanda reale ...
Felix Crazzolara,

Risposte:


134

Le istanze di Buffersono anche istanze diUint8Array in node.js 4.xe successive. Pertanto, la soluzione più efficiente è accedere buf.bufferdirettamente alla proprietà, come da https://stackoverflow.com/a/31394257/1375574 . Il costruttore Buffer accetta anche un argomento ArrayBufferView se è necessario andare nella direzione opposta.

Si noti che ciò non creerà una copia, il che significa che le scritture su qualsiasi ArrayBufferView scriveranno attraverso l'istanza Buffer originale.


Nelle versioni precedenti, node.js ha sia ArrayBuffer come parte di v8, ma la classe Buffer fornisce un'API più flessibile. Per leggere o scrivere su un ArrayBuffer, devi solo creare una vista e copiarla.

Da 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;
}

Da ArrayBuffer al 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;
}

5
Ti consiglierei anche di ottimizzarlo copiando numeri interi quando possibile usando DataView. Fino a size&0xfffffffe, copia numeri interi a 32 bit, quindi, se rimane 1 byte, copia numero intero a 8 bit, se 2 byte, copia numero intero a 16 bit e se 3 byte, copia numero intero a 16 bit e 8 bit.
Triang3l,

3
Vedi la risposta di kraag22 per un'implementazione più semplice di metà di questo.
OrangeDog,

Ho testato Buffer -> ArrayBuffer con un modulo destinato all'uso del browser e funziona alla perfezione. Grazie!
pospi,

3
Perché viene abrestituito? Non c'è niente da fare ab? Di conseguenza ottengo sempre {}.
Andi Giga,

1
"Il slice()metodo restituisce un nuovo il ArrayBuffercui contenuto è una copia di questi ArrayBufferbyte dall'inizio, inclusivo, fino alla fine, esclusivo." - MDNArrayBuffer.prototype.slice()
Константин Ван

61

Nessuna dipendenza, più veloce, Node.js 4.xe successive

Buffers sono Uint8Arrays, quindi devi solo tagliare (copiare) la sua regione del supporto 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);

Le slicecose offset e sono necessarie perché piccole Buffers (meno di 4 kB per impostazione predefinita, metà della dimensione del pool ) possono essere viste su un condiviso ArrayBuffer. Senza tagliare, puoi finire con un ArrayBufferdato contenente un altro Buffer. Vedi spiegazione nei documenti .

Se alla fine hai bisogno di un TypedArray, puoi crearne uno senza copiare i dati:

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

Nessuna dipendenza, velocità moderata, qualsiasi versione di Node.js

Usa la risposta di Martin Thomson , che corre tra O (n) time. (Vedi anche le mie risposte ai commenti sulla sua risposta sulle non ottimizzazioni. L'uso di un DataView è lento. Anche se è necessario capovolgere i byte, ci sono modi più veloci per farlo.)

Dipendenza, veloce, Node.js ≤ 0,12 o iojs 3.x

Puoi usare https://www.npmjs.com/package/memcpy per andare in entrambe le direzioni (da Buffer ad ArrayBuffer e viceversa). È più veloce delle altre risposte pubblicate qui ed è una libreria ben scritta. Il nodo da 0.12 a iojs 3.x richiede il fork di ngossen (vedi questo ).


Non compila di nuovo nodo> 0.12
Pawel Veselov

1
Usa la forcella di ngossen: github.com/dcodeIO/node-memcpy/pull/6 . Vedi anche la mia nuova risposta se stai usando il nodo 4+.
ZachB,

Dov'erano .byteLengthe .byteOffsetdocumentati?
Константин Ван,


1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);mi ha salvato la giornata
Alexey Sh.

56

"Da ArrayBuffer a Buffer" potrebbe essere fatto in questo modo:

var buffer = Buffer.from( new Uint8Array(ab) );

27
Questo è l'opposto di ciò che OP voleva.
Alexander Gonchiy,

43
Ma è quello che volevo cercare su Google il mio problema e sono contento di aver trovato la soluzione.
Maciej Krawczyk,

27

Un modo più veloce per scriverlo

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

Tuttavia, questo sembra funzionare circa 4 volte più lentamente della funzione suggerita toArrayBuffer su un buffer con 1024 elementi.


3
Aggiunta tardiva: @trevnorris dice "a partire da [V8] 4.3 I buffer sono supportati da Uint8Array", quindi probabilmente questo è più veloce ora ...
ChrisV

Vedi la mia risposta per il modo sicuro per farlo.
ZachB,

3
Provato con v5.6.0 ed è stato il più veloce
daksh_019 l'

1
Questo funziona solo perché le istanze di Buffersono anche istanze di Uint8Arrayin Node.js 4.xe successive. Per versioni precedenti di Node.js è necessario implementare una toArrayBufferfunzione.
Benny Neugebauer,

14

1. A Bufferè solo una vista per guardare in un ArrayBuffer.

A Buffer, infatti, è una FastBuffer, che extends(eredita da) Uint8Array, che è un ottetto unità view ( “di accesso parziale”) della memoria effettiva, un ArrayBuffer.

  📜 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. La dimensione di an ArrayBuffere la dimensione della sua vista possono variare.

Motivo # 1: Buffer.from(arrayBuffer[, byteOffset[, length]]).

Con Buffer.from(arrayBuffer[, byteOffset[, length]]), è possibile creare un Buffercon specificando il suo sottostante ArrayBuffere la posizione e le dimensioni della 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.

Motivo n. 2: FastBufferallocazione della memoria.

Alloca la memoria in due modi diversi a seconda delle dimensioni.

  • Se la dimensione è inferiore alla metà della dimensione di un pool di memoria e non è 0 ("piccola") : utilizza un pool di memoria per preparare la memoria richiesta.
  • Altrimenti : crea un apposito ArrayBufferche si adatta perfettamente alla memoria richiesta.
  📜 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));
}

Cosa intendi con " pool di memoria "?

Un pool di memoria è un blocco di memoria pre-allocato di dimensioni fisse per mantenere blocchi di memoria di piccole dimensioni per Buffers. Usandolo si mantengono strettamente i blocchi di memoria di piccole dimensioni, quindi si evita la frammentazione causata dalla gestione separata (allocazione e deallocazione) di blocchi di memoria di piccole dimensioni.

In questo caso, i pool di memoria sono di ArrayBufferdimensioni predefinite di 8 KiB, che è specificato in Buffer.poolSize. Quando deve fornire un blocco di memoria di piccole dimensioni per a Buffer, controlla se l'ultimo pool di memoria dispone di memoria sufficiente per gestirlo; in tal caso, crea un oggetto Bufferche "visualizza" il blocco parziale dato del pool di memoria, altrimenti crea un nuovo pool di memoria e così via.


È possibile accedere al sottostante ArrayBufferdi a Buffer. Il Buffer's bufferdi proprietà (che è, ereditato da Uint8Array) lo tiene. Un “piccolo” Buffer 's bufferproprietà è un ArrayBufferche rappresenta l'intero pool di memoria. Quindi, in questo caso, la ArrayBuffere Buffervaria in dimensioni.

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. Quindi abbiamo bisogno di estrarre la memoria che " vede ".

Un ArrayBufferè di dimensioni fisse, quindi è necessario estrarlo facendo una copia della parte. Per fare questo, usiamo Bufferla byteOffsetproprietà e la lengthproprietà , che sono ereditate da Uint8Array, e il ArrayBuffer.prototype.slicemetodo , che fa una copia di una parte di un ArrayBuffer. Il slice()metodo -ing qui è stato ispirato da @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. Miglioramento delle prestazioni

Se si desidera utilizzare i risultati in sola lettura o è corretto modificare il Buffercontenuto dell'input , è possibile evitare la copia di memoria non necessaria.

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

4
Va tutto bene ... ma hai davvero risposto alla domanda di OP? Se lo hai fatto, è sepolto ...
Tustin2121

Bella risposta! In obtain_arraybuffer: buf.buffer.subarraynon sembra esistere. Intendevi buf.buffer.slicequi?
ogni giorno produttivo dal

@everydayproductive Grazie. Come puoi vedere nella cronologia delle modifiche, l'ho effettivamente utilizzata ArrayBuffer.prototype.slicee successivamente modificata Uint8Array.prototype.subarray. Oh, e ho fatto male. Probabilmente allora era un po 'confuso. Va tutto bene adesso grazie a te.
Константин Ван


1

Puoi pensare a ArrayBuffercome a un dattiloscritto Buffer.

Un ArrayBufferpertanto necessita sempre di un tipo (la cosiddetta "Visualizzazione buffer array"). In genere, la vista buffer di array ha un tipo di Uint8Arrayo Uint16Array.

C'è un buon articolo di Renato Mangini sulla conversione tra un ArrayBuffer e una String .

Ho riassunto le parti essenziali in un esempio di codice (per Node.js). Mostra anche come convertire tra tipizzato ArrayBuffere non tipizzato 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!"

0

Ho provato quanto sopra per un Float64Array e semplicemente non ha funzionato.

Ho finito per rendermi conto che davvero i dati dovevano essere letti 'INTO' la vista in blocchi corretti. Ciò significa leggere 8 byte alla volta dal buffer di origine.

Comunque questo è quello che ho finito con ...

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++;
}

Ecco perché la risposta di Martin Thomson utilizza Uint8Array: è indipendente dalla dimensione degli elementi. Anche i Buffer.read*metodi sono tutti lenti.
ZachB,

Più viste array tipizzate possono fare riferimento allo stesso ArrayBuffer utilizzando la stessa memoria. Ogni valore in un buffer è di un byte, quindi è necessario inserirlo in un array con dimensione dell'elemento di 1 byte. È possibile utilizzare il metodo Martin, quindi creare un nuovo Float64Array utilizzando lo stesso arraybuffer nel costruttore.
ZachB,

0

Questo proxy esporrà il buffer come uno qualsiasi degli TypedArrays, senza alcuna copia. :

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

Funziona solo su LE, ma può essere facilmente portato su BE. Inoltre, non sono mai riuscito a testare quanto sia efficace.


1
Sebbene questo collegamento possa rispondere alla domanda, è meglio includere qui le parti essenziali della risposta e fornire il collegamento come riferimento. Le risposte di solo collegamento possono diventare non valide se la pagina collegata cambia
koceeng

2
La mia formulazione potrebbe non sembrare molto ufficiale, ma fornisce abbastanza informazioni per ricreare la soluzione. La soluzione si basa su JavaScript Proxy Object per avvolgere un buffer NodeJS nativo con getter e setter utilizzati da TypedArrays. Ciò rende l'istanza Buffer compatibile con qualsiasi libreria che richiede l'interfaccia Typed Array. Questa è la risposta che il poster originale sperava, ma sentiti libero di respingerlo perché non si adatta al tuo linguaggio accademico / aziendale. Vedi se mi interessa.
Dlabz,


-1

NodeJS, ad un certo punto (penso che fosse v0.6.x) aveva il supporto ArrayBuffer. Ho creato una piccola libreria per la codifica e decodifica Base64 qui , ma dall'aggiornamento alla v0.7, i test (su NodeJS) falliscono. Sto pensando di creare qualcosa che normalizzi questo, ma fino ad allora, suppongo che Bufferdovrebbe essere usato il nativo di Node .


-6

Ho già aggiornato il mio nodo alla versione 5.0.0 e lavoro con questo:

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 per controllare la mia immagine del disco VHD.


Sembra un metodo specializzato (e lento) basato sulla serializzazione, non un metodo generico per la conversione da / a Buffer / ArrayBuffer?
ZachB,

@ZachB è un metodo generico per V5.0.0 + [solo] = =.
Miguel Valentine,

toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- restituisce un array di stringhe, non numeri interi / byte.
ZachB,

@ZachB array di ritorno -> elenco di ritorno. riparo int-> string per stdout
Miguel Valentine

In tal caso è lo stesso di stackoverflow.com/a/19544002/1218408 e comunque senza necessità l'offset di byte controlla in stackoverflow.com/a/31394257/1218408 .
ZachB
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.