Konvertieren Sie einen binären NodeJS-Puffer in JavaScript ArrayBuffer

133

Wie kann ich einen NodeJS-Binärpuffer in einen JavaScript-ArrayBuffer konvertieren?

Drake Amara
quelle
1
Ich bin gespannt, warum Sie das tun müssen.
Chris Biscardi
14
Ein gutes Beispiel wäre das Schreiben einer Bibliothek, die mit Dateien in Browsern und auch für NodeJS-Dateien funktioniert.
fbstj
1
oder mit einer Browser-Bibliothek in NodeJS
OrangeDog
1
Ein weiterer Grund ist, dass ein Float zu viele Bytes RAM benötigt, wenn er in einem gespeichert wird Array. Um also viele Floats zu speichern, benötigen Float32ArraySie 4 Bytes. Und wenn Sie eine schnelle Serialisierung dieser Floats in eine Datei wünschen, benötigen Sie eine Buffer, da die Serialisierung in JSON ewig dauert.
Nponeccop
Ich möchte genau das Gleiche wissen, um generische Daten mit WebRTC zu senden, und es ist unglaublich, dass so viele Antworten hier so viele Likes haben, aber die eigentliche Frage nicht beantworten ...
Felix Crazzolara

Antworten:

134

Instanzen von Buffersind auch Instanzen vonUint8Array in node.js 4.x und höher. Die effizienteste Lösung ist daher der buf.bufferdirekte Zugriff auf die Eigenschaft gemäß https://stackoverflow.com/a/31394257/1375574 . Der Buffer-Konstruktor verwendet auch ein ArrayBufferView-Argument, wenn Sie in die andere Richtung gehen müssen.

Beachten Sie, dass dadurch keine Kopie erstellt wird. Dies bedeutet, dass beim Schreiben in eine ArrayBufferView in die ursprüngliche Buffer-Instanz geschrieben wird.


In älteren Versionen hat node.js beide ArrayBuffer als Teil von v8, aber die Buffer-Klasse bietet eine flexiblere API. Zum Lesen oder Schreiben in einen ArrayBuffer müssen Sie nur eine Ansicht erstellen und kopieren.

Vom Puffer zum 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;
}

Von ArrayBuffer zu 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
quelle
5
Ich würde Ihnen auch empfehlen, dies zu optimieren, indem Sie nach Möglichkeit Ganzzahlen mit DataView kopieren. Bis size&0xfffffffe32-Bit-Ganzzahlen kopieren, dann, wenn noch 1 Byte übrig ist, 8-Bit-Ganzzahl kopieren, wenn 2 Byte, 16-Bit-Ganzzahl kopieren, und wenn 3 Byte, 16-Bit- und 8-Bit-Ganzzahl kopieren.
Triang3l
3
In der Antwort von kraag22 finden Sie eine einfachere Implementierung der Hälfte davon.
OrangeDog
Haben Buffer -> ArrayBuffer mit einem Modul getestet, das für die Verwendung im Browser vorgesehen ist, und es funktioniert hervorragend. Vielen Dank!
Pospi
3
Warum wird abzurückgegeben? Es ist nichts damit gemacht ab? Ich bekomme immer {}als Ergebnis.
Andi Giga
1
'Die slice()Methode gibt eine neue zurück, ArrayBufferderen Inhalt eine Kopie dieser ArrayBufferBytes von Anfang, einschließlich, bis Ende, exklusiv ist.' - MDNArrayBuffer.prototype.slice()
23онстантин Ван
61

Keine Abhängigkeiten, am schnellsten, Node.js 4.x und höher

Buffers sind Uint8Arrays, Sie müssen also nur den Bereich des Hintergrunds in Scheiben schneiden (kopieren) 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);

Das sliceund Offset-Material ist erforderlich, da kleine Buffers (standardmäßig weniger als 4 kB, die Hälfte der Poolgröße ) Ansichten auf einer gemeinsam genutzten Datei sein können ArrayBuffer. Ohne Slicing können Sie ArrayBufferDaten von einem anderen erhalten Buffer. Siehe Erklärung in den Dokumenten .

Wenn Sie letztendlich eine benötigen TypedArray, können Sie eine erstellen, ohne die Daten zu kopieren:

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

Keine Abhängigkeiten, mäßige Geschwindigkeit, jede Version von Node.js.

Verwenden Sie die Antwort von Martin Thomson , die in O (n) Zeit ausgeführt wird. (Siehe auch meine Antworten auf Kommentare zu seiner Antwort zu Nichtoptimierungen. Die Verwendung einer DataView ist langsam. Selbst wenn Sie Bytes umdrehen müssen, gibt es schnellere Möglichkeiten.)

Abhängigkeit, schnell, Node.js ≤ 0,12 oder iojs 3.x.

Sie können https://www.npmjs.com/package/memcpy verwenden , um in beide Richtungen zu gelangen (Buffer to ArrayBuffer und zurück). Es ist schneller als die anderen hier veröffentlichten Antworten und eine gut geschriebene Bibliothek. Knoten 0.12 bis iojs 3.x erfordern die Gabel von ngossen (siehe hier ).

ZachB
quelle
Es wird nicht wieder kompiliert Knoten> 0.12
Pawel Veselov
1
Verwenden Sie die Gabel von ngossen: github.com/dcodeIO/node-memcpy/pull/6 . Siehe auch meine neue Antwort, wenn Sie Knoten 4+ verwenden.
ZachB
Wo waren die .byteLengthund .byteOffsetdokumentiert?
23онстантин Ван
1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);hat meinen Tag gerettet
Alexey Sh.
55

"Von ArrayBuffer zu Puffer" könnte folgendermaßen erfolgen:

var buffer = Buffer.from( new Uint8Array(ab) );
kraag22
quelle
27
Das ist das Gegenteil von dem, was OP wollte.
Alexander Gonchiy
42
Aber genau das wollte ich googeln und bin froh, dass ich die Lösung gefunden habe.
Maciej Krawczyk
27

Ein schnellerer Weg, um es zu schreiben

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

Dies scheint jedoch ungefähr viermal langsamer zu laufen als die vorgeschlagene Funktion toArrayBuffer für einen Puffer mit 1024 Elementen.

David Fooks
quelle
3
Späte Hinzufügung: @trevnorris sagt "ab [V8] 4.3 Puffer werden von Uint8Array unterstützt", also ist dies möglicherweise jetzt schneller ...
ChrisV
Siehe meine Antwort für den sicheren Weg, dies zu tun.
ZachB
3
Getestet mit v5.6.0 und es war das schnellste
daksh_019
1
Dies funktioniert nur, weil Instanzen von Bufferauch Instanzen von Uint8Arrayin Node.js 4.x und höher sind. Für niedrigere Node.js-Versionen müssen Sie eine toArrayBufferFunktion implementieren .
Benny Neugebauer
14

1. A. Buffer ist nur eine Ansicht zum Betrachten eines ArrayBuffer.

A Bufferist in der Tat ein FastBuffer, das extends(erbt von) Uint8Array, was eine Oktetteinheit ist Ansicht ( „partial Accessor“) des Ist - Speicher, eineArrayBuffer .

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

Buffer.prototype = FastBuffer.prototype;

2. Die Größe eines ArrayBufferund die Größe seiner Ansicht können variieren.

Grund Nr. 1: Buffer.from(arrayBuffer[, byteOffset[, length]]) .

Mit Buffer.from(arrayBuffer[, byteOffset[, length]])können Sie ein Buffermit Angabe des Basiswerts erstellenArrayBuffer sowie der Position und Größe der Ansicht .

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.

Grund Nr. 2: FastBuffer Speicherzuordnung.

Der Speicher wird je nach Größe auf zwei verschiedene Arten zugewiesen.

  • Wenn die Größe weniger als die Hälfte der Größe eines Speicherpools beträgt und nicht 0 („klein“) ist, wird a verwendet Speicherpool verwendet , um den erforderlichen Speicher vorzubereiten.
  • Sonst : es schafft eine dedizierteArrayBuffer Speicher , der genau zum erforderlichen Speicher passt.
  📜 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));
}

Was meinst du mit einem „ Speicherpool "?

Ein Speicherpool ist ein vorab zugewiesener Speicherblock mit fester Größe , um kleine Speicherblöcke für Buffers aufzubewahren . Durch die Verwendung werden die kleinen Speicherblöcke eng zusammengehalten, wodurch dies verhindert wird Fragmentierung die durch die separate Verwaltung (Zuweisung und Freigabe) kleiner Speicherblöcke verursacht wird.

In diesem Fall handelt es sich bei den Speicherpools um ArrayBuffers, deren Größe standardmäßig 8 KB beträgt Buffer.poolSize. Dies ist in angegeben . Wenn ein kleiner Speicherblock für a Bufferbereitgestellt werden soll, wird geprüft, ob der letzte Speicherpool über genügend verfügbaren Speicher verfügt, um dies zu handhaben. In diesem Fall wird ein Element erstellt Buffer, das den angegebenen Teil des Speicherpools "anzeigt". Andernfalls wird ein neuer Speicherpool erstellt und so weiter.


Sie können auf den Basiswert ArrayBuffervon a zugreifen Buffer. Die Buffer‚s - bufferEigenschaft (das heißt, geerbt von Uint8Array) hält. Eine „kleine“ Buffer ‚s - bufferEigenschaft ist eine ArrayBuffer, die den gesamten Speicher - Pool darstellt. In diesem Fall variiert das ArrayBufferund das Bufferin der Größe.

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. Wir müssen also den Speicher extrahieren, den es " ansieht ".

An ArrayBufferhat eine feste Größe, daher müssen wir es herausziehen, indem wir eine Kopie des Teils erstellen. Dazu verwenden wir Buffer‚s byteOffsetEigentum und lengthImmobilien , die von vererbt werden Uint8Array, und das ArrayBuffer.prototype.sliceVerfahren , das eine Kopie eines Teils eines macht ArrayBuffer. Die hier beschriebene slice()-ing-Methode wurde von @ZachB inspiriert .

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. Leistungsverbesserung

Wenn Sie die Ergebnisse schreibgeschützt verwenden oder den BufferInhalt der Eingabe ändern möchten , können Sie unnötiges Kopieren des Speichers vermeiden.

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
Константин Ван
quelle
4
Das ist alles schön und gut ... aber haben Sie tatsächlich die Frage von OP beantwortet? Wenn Sie getan haben, ist es begraben ...
Tustin2121
Gute Antwort! In obtain_arraybuffer: buf.buffer.subarrayscheint nicht zu existieren. Meinten Sie buf.buffer.slicehier?
jeden Tag produktiv
@everydayproductive Danke. Wie Sie im Bearbeitungsverlauf sehen können, habe ich ihn tatsächlich verwendet ArrayBuffer.prototype.sliceund später geändert Uint8Array.prototype.subarray. Oh, und ich habe es falsch gemacht. Wahrscheinlich war ich damals etwas verwirrt. Dank dir ist jetzt alles gut.
8онстантин Ван
12

Verwenden Sie das folgende ausgezeichnete npm-Paket : to-arraybuffer.

Oder Sie können es selbst implementieren. Wenn Ihr Puffer aufgerufen wird buf, gehen Sie folgendermaßen vor:

buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
Feross
quelle
21
Joyent nahm es heraus
Aleclarson
1

Sie können sich eine ArrayBufferals getippt vorstellen Buffer.

Ein benötigt ArrayBufferdaher immer einen Typ (die sogenannte "Array Buffer View"). In der Regel hat die Array-Pufferansicht den Typ Uint8Arrayoder Uint16Array.

Es gibt einen guten Artikel von Renato Mangini über die Konvertierung zwischen einem ArrayBuffer und einem String .

Ich habe die wesentlichen Teile in einem Codebeispiel (für Node.js) zusammengefasst. Es wird auch gezeigt, wie zwischen typisiert ArrayBufferund untypisiert konvertiert wird 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
quelle
0

Ich habe das oben für ein Float64Array ausprobiert und es hat einfach nicht funktioniert.

Am Ende wurde mir klar, dass die Daten wirklich in korrekten Blöcken in die Ansicht gelesen werden mussten. Dies bedeutet, dass jeweils 8 Bytes aus dem Quellpuffer gelesen werden.

Jedenfalls ist es das, was ich am Ende hatte ...

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
quelle
Aus diesem Grund verwendet Martin Thomsons Antwort Uint8Array - es ist unabhängig von der Größe der Elemente. Die Buffer.read*Methoden sind auch alle langsam.
ZachB
Mehrere typisierte Array-Ansichten können mit demselben Speicher auf denselben ArrayBuffer verweisen. Jeder Wert in einem Puffer ist ein Byte, daher müssen Sie ihn in ein Array mit einer Elementgröße von 1 Byte einfügen. Sie können Martins Methode verwenden und dann ein neues Float64Array mit demselben Array-Puffer im Konstruktor erstellen.
ZachB
0

Dieser Proxy macht den Puffer als eines der TypedArrays ohne Kopie verfügbar. ::

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

Es funktioniert nur mit LE, kann aber problemlos auf BE portiert werden. Außerdem musste ich nie testen, wie effizient dies ist.

Dlabz
quelle
1
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verlinkte Seite ändert
koceeng
2
Mein Wortlaut klingt vielleicht nicht sehr offiziell, bietet aber genügend Informationen, um die Lösung neu zu erstellen. Die Lösung basiert auf JavaScript Proxy Object, um einen nativen NodeJS-Puffer mit Gettern und Setzern zu versehen, die von TypedArrays verwendet werden. Dadurch ist die Pufferinstanz mit jeder Bibliothek kompatibel, für die eine Typed Array-Schnittstelle erforderlich ist. Dies ist die Antwort, auf die das Originalplakat gehofft hatte, aber Sie können es gerne ablehnen, da es nicht zu Ihrer akademischen / geschäftlichen Sprache passt. Sehen Sie, ob es mich interessiert.
Dlabz
0

Jetzt gibt es dafür ein sehr nützliches npm-Paket: buffer https://github.com/feross/buffer

Es wird versucht, eine API bereitzustellen, die zu 100% mit der Puffer-API des Knotens identisch ist, und Folgendes zuzulassen:

und einige mehr.

Gopalakrishna Palem
quelle
-1

NodeJS hatte zu einem bestimmten Zeitpunkt (ich glaube es war v0.6.x) ArrayBuffer-Unterstützung. Ich habe eine kleine Bibliothek für Base64 - Codierung und Decodierung hier , sondern auf v0.7 seit der Aktualisierung die Tests (auf NodeJS) scheitern. Ich denke darüber nach, etwas zu schaffen, das dies normalisiert, aber bis dahin Buffersollte wohl Node's Native verwendet werden.

arunjitsingh
quelle
-6

Ich habe meinen Knoten bereits auf Version 5.0.0 aktualisiert und arbeite damit:

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
}

Ich benutze es, um mein VHD-Image zu überprüfen.

Miguel Valentine
quelle
Dies scheint eine spezialisierte (und langsame) serialisierungsbasierte Methode zu sein, keine generische Methode zum Konvertieren in / von Buffer / ArrayBuffer?
ZachB
@ZachB ist eine generische Methode für V5.0.0 + [nur] = =.
Miguel Valentine
toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- Dies gibt ein Array von Zeichenfolgen zurück, keine Ganzzahlen / Bytes.
ZachB
@ZachB Rückgabearray -> Rückgabeliste. Ich repariere int-> string für stdout
Miguel Valentine
In diesem Fall ist es dasselbe wie stackoverflow.com/a/19544002/1218408 , und der Byte-Offset wird immer noch ohne Notwendigkeit in stackoverflow.com/a/31394257/1218408 überprüft .
ZachB