Convertir un tampon NodeJS binaire en ArrayBuffer JavaScript

133

Comment puis-je convertir un tampon binaire NodeJS en un ArrayBuffer JavaScript?

Drake Amara
la source
1
Je suis curieux de savoir pourquoi vous auriez besoin de faire cela?
Chris Biscardi
14
un bon exemple serait l'écriture d'une bibliothèque qui fonctionnait avec les fichiers dans les navigateurs et aussi pour les fichiers NodeJS?
fbstj
1
ou en utilisant une bibliothèque de navigateur dans NodeJS
OrangeDog
1
Une autre raison est qu'un float prend trop d'octets de RAM lorsqu'il est stocké dans un fichier Array. Donc, pour stocker de nombreux flottants, vous avez besoin de Float32Array4 octets. Et si vous voulez une sérialisation rapide de ces flottants dans un fichier, vous en avez besoin Buffer, car la sérialisation vers JSON prend des années.
nponeccop
Je veux savoir exactement la même chose pour envoyer des données génériques en utilisant WebRTC et c'est incroyable que tant de réponses ici aient autant de goûts, mais ne répondez pas à la question réelle ...
Felix Crazzolara

Réponses:

134

Les instances de Buffersont également des instances deUint8Array dans node.js 4.x et supérieur. Ainsi, la solution la plus efficace consiste à accéder buf.bufferdirectement à la propriété, selon https://stackoverflow.com/a/31394257/1375574 . Le constructeur Buffer prend également un argument ArrayBufferView si vous devez aller dans l'autre direction.

Notez que cela ne créera pas de copie, ce qui signifie que les écritures dans n'importe quel ArrayBufferView écriront dans l'instance Buffer d'origine.


Dans les anciennes versions, node.js a à la fois ArrayBuffer dans le cadre de la v8, mais la classe Buffer fournit une API plus flexible. Pour lire ou écrire dans un ArrayBuffer, il vous suffit de créer une vue et de copier.

De Buffer à 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 à 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
la source
5
Je vous recommande également d'optimiser cela en copiant des entiers lorsque cela est possible à l'aide de DataView. Jusqu'à ce que size&0xfffffffe, copiez les entiers 32 bits, puis, s'il reste 1 octet, copiez l'entier 8 bits, si 2 octets, copiez l'entier 16 bits, et si 3 octets, copiez les entiers 16 bits et 8 bits.
Triang3l
3
Voir la réponse de kraag22 pour une implémentation plus simple de la moitié de cela.
OrangeDog
J'ai testé Buffer -> ArrayBuffer avec un module destiné à une utilisation par navigateur et il fonctionne à merveille. Merci!
pospi
3
Pourquoi est abretourné? Il n'y a rien avec ab? Je reçois toujours {}en conséquence.
Andi Giga
1
'La slice()méthode retourne un nouveau ArrayBufferdont le contenu est une copie des ArrayBufferoctets de this de begin, inclusive, up to end, exclusive.' - MDNArrayBuffer.prototype.slice()
Константин Ван
61

Aucune dépendance, plus rapide, Node.js 4.x et versions ultérieures

Buffers sont des Uint8Arrays, il vous suffit donc de découper (copier) sa région du support 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);

Les slicetrucs et offset sont nécessaires car les petits Buffers (moins de 4 Ko par défaut, la moitié de la taille du pool ) peuvent être des vues sur un partage ArrayBuffer. Sans découpage, vous pouvez vous retrouver avec un ArrayBuffercontenant des données d'un autre Buffer. Voir l' explication dans la documentation .

Si vous en avez finalement besoin TypedArray, vous pouvez en créer un sans copier les données:

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

Aucune dépendance, vitesse modérée, toute version de Node.js

Utilisez la réponse de Martin Thomson , qui s'exécute dans le temps O (n) . (Voir aussi mes réponses aux commentaires sur sa réponse sur les non-optimisations. L'utilisation d'un DataView est lente. Même si vous devez inverser des octets, il existe des moyens plus rapides de le faire.)

Dépendance, rapide, Node.js ≤ 0,12 ou iojs 3.x

Vous pouvez utiliser https://www.npmjs.com/package/memcpy pour aller dans les deux sens (Buffer vers ArrayBuffer et retour). C'est plus rapide que les autres réponses publiées ici et c'est une bibliothèque bien écrite. Les nœuds 0.12 à iojs 3.x nécessitent le fork de ngossen (voir ceci ).

ZachB
la source
Il ne compile pas à nouveau node> 0.12
Pawel Veselov
1
Utilisez le fork de ngossen : github.com/dcodeIO/node-memcpy/pull/6 . Voir aussi ma nouvelle réponse si vous utilisez le nœud 4+.
ZachB
Où étaient les .byteLengthet .byteOffsetdocumentés?
Константин Ван
1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);m'a sauvé la journée
Alexey Sh.
56

"From ArrayBuffer to Buffer" pourrait être fait de cette façon:

var buffer = Buffer.from( new Uint8Array(ab) );
kraag22
la source
27
C'est le contraire de ce que voulait OP.
Alexander Gonchiy
43
Mais c'est ce que je voulais googler mon problème et heureux d'avoir trouvé la solution.
Maciej Krawczyk
27

Une façon plus rapide de l'écrire

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

Cependant, cela semble fonctionner environ 4 fois plus lentement que la fonction toArrayBuffer suggérée sur un tampon avec 1024 éléments.

David Fooks
la source
3
Ajout tardif: @trevnorris dit "à partir de [V8] 4.3 Les tampons sont soutenus par Uint8Array", donc c'est peut-être plus rapide maintenant ...
ChrisV
Consultez ma réponse pour savoir comment procéder en toute sécurité.
ZachB
3
Je l'ai testé avec la v5.6.0 et c'était le plus rapide
daksh_019
1
Cela ne fonctionne que car les instances de Buffersont également des instances de Uint8Arraydans Node.js 4.x et supérieur. Pour les versions inférieures de Node.js, vous devez implémenter une toArrayBufferfonction.
Benny Neugebauer
14

1. A Bufferest juste une vue pour regarder dans un fichier ArrayBuffer.

A Buffer, en fait, est a FastBuffer, qui extends(hérite de) Uint8Array, qui est une vue d' unité d'octet («accesseur partiel») de la mémoire réelle, an 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 taille d'un ArrayBufferet la taille de sa vue peuvent varier.

Raison n ° 1: Buffer.from(arrayBuffer[, byteOffset[, length]]).

Avec Buffer.from(arrayBuffer[, byteOffset[, length]]), vous pouvez créer un Bufferavec en spécifiant son sous ArrayBuffer- jacent ainsi que la position et la taille de la vue.

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.

Raison n ° 2: FastBufferl'allocation de mémoire de.

Il alloue la mémoire de deux manières différentes en fonction de la taille.

  • Si la taille est inférieure à la moitié de la taille d'un pool de mémoire et n'est pas 0 («petit») : il utilise un pool de mémoire pour préparer la mémoire requise.
  • Sinon : il crée un dédié ArrayBufferqui correspond exactement à la mémoire requise.
  📜 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'entendez-vous par « pool de mémoire »?

Un pool de mémoire est un bloc de mémoire pré-alloué de taille fixe pour conserver des blocs de mémoire de petite taille pendant Buffers. Son utilisation permet de maintenir les blocs de mémoire de petite taille étroitement ensemble, évitant ainsi la fragmentation causée par la gestion séparée (allocation et désallocation) des blocs de mémoire de petite taille.

Dans ce cas, les pools de mémoire sont des ArrayBuffers dont la taille par défaut est de 8 Kio, ce qui est spécifié dans Buffer.poolSize. Lorsqu'il doit fournir un bloc de mémoire de petite taille pour a Buffer, il vérifie si le dernier pool de mémoire dispose de suffisamment de mémoire disponible pour gérer cela; si c'est le cas, il crée un Bufferqui «visualise» le bloc partiel donné du pool de mémoire, sinon, il crée un nouveau pool de mémoire et ainsi de suite.


Vous pouvez accéder au sous-jacent ArrayBufferd'un Buffer. La propriétéBuffer s (c'est-à-dire héritée de ) la détient. Un « petit » de propriété est un qui représente l'ensemble du pool de mémoire. Donc, dans ce cas, le et le varient en taille.bufferUint8Array BufferbufferArrayBufferArrayBufferBuffer

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. Nous devons donc extraire la mémoire qu'il « visualise ».

An ArrayBufferest de taille fixe, nous devons donc l'extraire en faisant une copie de la pièce. Pour ce faire, nous utilisons Bufferla byteOffsetpropriété et la lengthpropriété de , qui sont héritées de Uint8Array, et la ArrayBuffer.prototype.sliceméthode , qui fait une copie d'une partie d'un fichier ArrayBuffer. La slice()méthode -ing ici a été inspirée par @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. Amélioration des performances

Si vous utilisez les résultats en lecture seule ou siBuffer vous pouvez modifier le contenu des entrées , vous pouvez éviter une copie inutile de la mémoire.

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
Константин Ван
la source
4
Tout cela est bien beau ... mais avez-vous réellement répondu à la question d'OP? Si vous l'avez fait, il est enterré ...
Tustin2121
Très bonne réponse! In obtain_arraybuffer: buf.buffer.subarrayne semble pas exister. Voulez-vous dire buf.buffer.sliceici?
productif tous les jours le
@everydayproductive Merci. Comme vous pouvez le voir dans l'historique des modifications, je l'ai en fait utilisé ArrayBuffer.prototype.sliceet modifié plus tard en Uint8Array.prototype.subarray. Oh, et je l'ai mal fait. Probablement un peu confus à l'époque. Tout va bien maintenant grâce à vous.
Константин Ван
12

Utilisez le paquet excellent NPM suivant: to-arraybuffer.

Ou vous pouvez le mettre en œuvre vous-même. Si votre tampon est appelé buf, procédez comme suit:

buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
Feross
la source
21
Joyent a sorti
aleclarson
1

Vous pouvez considérer un ArrayBuffercomme un fichier tapé Buffer.

An a ArrayBufferdonc toujours besoin d'un type (appelé "Array Buffer View"). En règle générale, la vue de la mémoire tampon du tableau a un type de Uint8Arrayou Uint16Array.

Il y a un bon article de Renato Mangini sur la conversion entre un ArrayBuffer et une String .

J'ai résumé les parties essentielles dans un exemple de code (pour Node.js). Il montre également comment convertir entre le typé ArrayBufferet le non typé 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
la source
0

J'ai essayé ce qui précède pour un Float64Array et cela ne fonctionnait tout simplement pas.

J'ai fini par réaliser que vraiment les données devaient être lues «DANS» la vue en morceaux corrects. Cela signifie lire 8 octets à la fois à partir du tampon source.

Bref, c'est ce avec quoi j'ai fini ...

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
la source
C'est pourquoi la réponse de Martin Thomson utilise Uint8Array - il est indépendant de la taille des éléments. Les Buffer.read*méthodes sont toutes lentes également.
ZachB
Plusieurs vues de tableau typées peuvent référencer le même ArrayBuffer en utilisant la même mémoire. Chaque valeur dans un tampon est d'un octet, vous devez donc la placer dans un tableau avec une taille d'élément de 1 octet. Vous pouvez utiliser la méthode de Martin, puis créer un nouveau Float64Array en utilisant le même arraybuffer dans le constructeur.
ZachB
0

Ce proxy exposera le tampon comme l'un des TypedArrays, sans aucune copie. :

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

Cela ne fonctionne que sur LE, mais peut être facilement porté vers BE. De plus, je n'ai jamais eu à tester l'efficacité de cette méthode.

Dlabz
la source
1
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change
koceeng
2
Ma formulation peut ne pas sembler très officielle, mais elle fournit suffisamment d'informations pour recréer la solution. La solution repose sur JavaScript Proxy Object pour encapsuler un tampon NodeJS natif avec des getters et des setters utilisés par TypedArrays. Cela rend l'instance Buffer compatible avec toute bibliothèque nécessitant une interface Typed Array. C'est la réponse que l'affiche originale espérait, mais n'hésitez pas à la rejeter car elle ne correspond pas à votre jargon universitaire / d'entreprise. Voyez si je m'en soucie.
Dlabz
-1

NodeJS, à un moment donné (je pense que c'était v0.6.x) avait le support ArrayBuffer. J'ai créé une petite bibliothèque pour l'encodage et le décodage base64 ici , mais depuis la mise à jour vers la v0.7, les tests (sur NodeJS) échouent. Je pense créer quelque chose qui normalise cela, mais jusque-là, je suppose que le natif de Node Bufferdevrait être utilisé.

arunjitsingh
la source
-6

J'ai déjà mis à jour mon nœud vers la version 5.0.0 et je travaille avec ceci:

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
}

Je l'utilise pour vérifier mon image disque vhd.

Miguel Valentine
la source
Cela ressemble à une méthode spécialisée (et lente) basée sur la sérialisation, pas à une méthode générique pour la conversion vers / depuis Buffer / ArrayBuffer?
ZachB
@ZachB c'est une méthode générique pour V5.0.0 + [seulement] = =.
Miguel Valentine du
toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- cela renvoie un tableau de chaînes, pas des entiers / octets.
ZachB
@ZachB return array -> return list. i fix int-> string for stdout
Miguel Valentine
Dans ce cas, c'est la même chose que stackoverflow.com/a/19544002/1218408 , et toujours sans nécessaire les vérifications de décalage d'octet dans stackoverflow.com/a/31394257/1218408 .
ZachB