Converter um buffer NodeJS binário em JavaScript ArrayBuffer

133

Como converter um buffer binário do NodeJS em um JavaScript ArrayBuffer?

Drake Amara
fonte
1
Estou curioso para saber por que você precisaria fazer isso?
22611 Chris Biscardi
14
Um bom exemplo seria escrever uma biblioteca que funcionasse com os arquivos nos navegadores e também para arquivos NodeJS?
Fbstj
1
ou usando uma biblioteca de navegador em NodeJS
OrangeDog
1
Outro motivo é que um flutuador leva muitos bytes de RAM quando armazenado em um Array. Portanto, para armazenar muitos carros alegóricos, é necessário Float32Arrayonde são necessários 4 bytes. E se você deseja serializar rapidamente esses flutuadores em um arquivo, precisará de um Buffer, pois a serialização para JSON leva séculos.
Nspeccop
Eu quero saber exatamente a mesma coisa para enviar dados genéricos usando WebRTC e é inacreditável que tantas respostas aqui têm tantos gostos, mas não responder a pergunta real ...
Felix Crazzolara

Respostas:

134

Instâncias de Buffertambém são instânciasUint8Array no node.js 4.xe superior. Portanto, a solução mais eficiente é acessar a buf.bufferpropriedade diretamente, conforme https://stackoverflow.com/a/31394257/1375574 . O construtor Buffer também aceita um argumento ArrayBufferView se você precisar ir na outra direção.

Observe que isso não criará uma cópia, o que significa que as gravações em qualquer ArrayBufferView serão gravadas na instância original do Buffer.


Nas versões anteriores, o node.js possui o ArrayBuffer como parte da v8, mas a classe Buffer fornece uma API mais flexível. Para ler ou gravar em um ArrayBuffer, você só precisa criar uma exibição e copiar.

Do buffer para o 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 para 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
fonte
5
Também recomendo que você otimize isso copiando números inteiros quando possível usando o DataView. Até que size&0xfffffffe, copie números inteiros de 32 bits; se houver 1 byte restante, copie o número inteiro de 8 bits; se 2 bytes, copie o número inteiro de 16 bits; e, se 3 bytes, copie o número inteiro de 16 e 8 bits.
Triang3l
3
Veja a resposta de kraag22 para uma implementação mais simples de metade disso.
OrangeDog
Testaram o Buffer -> ArrayBuffer com um módulo destinado ao uso do navegador e está funcionando de maneira brilhante. Obrigado!
pospi
3
Por que é abdevolvido? Não há nada feito ab? Eu sempre recebo {}como resultado.
Andi Giga
1
'O slice()método retorna um novo ArrayBuffercujo conteúdo é uma cópia dos ArrayBufferbytes deste, desde o início, inclusive, até o fim, exclusivo.' - MDNArrayBuffer.prototype.slice()
Константин Ван 23/01
61

Sem dependências, mais rápido, Node.js 4.xe posterior

Buffers são Uint8Arrays, então você só precisa cortar (copiar) sua região do suporte 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);

O slicematerial e offset são necessários porque Buffers pequenos (menos de 4 kB por padrão, metade do tamanho do pool ) podem ser vistas em um compartilhamento ArrayBuffer. Sem fatiar, você pode acabar com um ArrayBufferdado contendo outro Buffer. Veja a explicação nos documentos .

Se você precisar de um TypedArray, poderá criar um sem copiar os dados:

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

Sem dependências, velocidade moderada, qualquer versão do Node.js

Use a resposta de Martin Thomson , que é executada em O (n) tempo. (Veja também minhas respostas aos comentários sobre a resposta dele sobre não otimizações. O uso de um DataView é lento. Mesmo se você precisar inverter bytes, existem maneiras mais rápidas de fazer isso.)

Dependência, rápida, Node.js ≤ 0.12 ou iojs 3.x

Você pode usar https://www.npmjs.com/package/memcpy para ir em qualquer direção (Buffer para ArrayBuffer e vice-versa). É mais rápido que as outras respostas postadas aqui e é uma biblioteca bem escrita. O nó 0.12 a iojs 3.x requer o fork do ngossen (consulte isso ).

ZachB
fonte
Ele não compila novamente o nó> 0,12
Pawel Veselov
1
Use o fork do ngossen : github.com/dcodeIO/node-memcpy/pull/6 . Veja também minha nova resposta se você estiver usando o nó 4+.
ZachB
Onde estavam .byteLengthe .byteOffsetdocumentados?
Jun
1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);salvou meu dia
Alexey Sh.
56

"De ArrayBuffer para Buffer" pode ser feito desta maneira:

var buffer = Buffer.from( new Uint8Array(ab) );
kraag22
fonte
27
É o oposto do que o OP queria.
Alexander Gonchiy
43
Mas era isso que eu queria pesquisar no meu problema e feliz por ter encontrado a solução.
Maciej Krawczyk 22/03
27

Uma maneira mais rápida de escrever

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

No entanto, isso parece ser executado aproximadamente quatro vezes mais lento que a função toArrayBuffer sugerida em um buffer com 1024 elementos.

David Fooks
fonte
3
Além tardio: @trevnorris diz "a partir de [V8] 4.3 Buffers são apoiados por Uint8Array", então possivelmente este é mais rápido agora ...
ChrisV
Veja minha resposta para a maneira segura de fazer isso.
ZachB
3
Testei com v5.6.0 e foi o mais rápido
daksh_019
1
Isso funciona apenas porque instâncias de Buffertambém são instâncias do Uint8ArrayNode.js. 4.xe superior. Para versões inferiores do Node.js., você precisa implementar uma toArrayBufferfunção.
Benny Neugebauer
14

1. A Bufferé apenas uma visão para analisar um ArrayBuffer.

A Buffer, de fato, é a FastBuffer, que extends(herda de) Uint8Array, que é uma visão da unidade de octetos ("acessador parcial") da memória real, an ArrayBuffer.

  Ode 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. O tamanho de um ArrayBuffere o tamanho de sua visualização podem variar.

Razão # 1: Buffer.from(arrayBuffer[, byteOffset[, length]]).

Com Buffer.from(arrayBuffer[, byteOffset[, length]]), você pode criar um Buffercom especificando sua posição subjacente ArrayBuffere a posição e tamanho da exibição.

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ão # 2: FastBufferalocação de memória.

Ele aloca a memória de duas maneiras diferentes, dependendo do tamanho.

  • Se o tamanho for menor que a metade do tamanho de um conjunto de memórias e não for 0 (“pequeno”) : ele utiliza um conjunto de memórias para preparar a memória necessária.
  • Senão : ele cria um dedicado ArrayBufferque se encaixa exatamente na memória necessária.
  Ode 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);
  }
}
  Ode Node.js 9.4.0/lib/buffer.js#L98-L100
function createUnsafeBuffer(size) {
  return new FastBuffer(createUnsafeArrayBuffer(size));
}

O que você quer dizer com " pool de memória "?

Um conjunto de memórias é um bloco de memória pré-alocado de tamanho fixo para manter pequenos blocos de memória por Buffers. O uso mantém os blocos de memória de tamanho pequeno firmemente juntos, evitando a fragmentação causada pelo gerenciamento separado (alocação e desalocação) de blocos de memória de tamanho pequeno.

Neste caso, os pools de memória são ArrayBuffers cujo tamanho é de 8 KiB por padrão, que é especificado em Buffer.poolSize. Quando é para fornecer um pedaço de memória de tamanho pequeno para a Buffer, ele verifica se o último pool de memória possui memória disponível suficiente para lidar com isso; nesse caso, cria um Bufferque "visualiza" o pedaço parcial fornecido do conjunto de memórias; caso contrário, cria um novo conjunto de memórias e assim por diante.


Você pode acessar o subjacente ArrayBufferde a Buffer. O Buffer's bufferpropriedade (ou seja, herdada Uint8Array) prende-lo. Um “pequeno” Buffer da bufferpropriedade é um ArrayBufferque representa todo o pool de memória. Portanto, neste caso, o ArrayBuffere Buffervaria em tamanho.

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. Portanto, precisamos extrair a memória que " visualiza ".

Como o ArrayBuffertamanho é fixo, precisamos extraí-lo fazendo uma cópia da peça. Para fazer isso, usamos Buffer's byteOffsetpropriedade e lengthpropriedade , que são herdadas Uint8Array, e o ArrayBuffer.prototype.slicemétodo , que faz uma cópia de uma parte de um ArrayBuffer. O slice()método -ing aqui foi inspirado no @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. Melhoria de desempenho

Se você usar os resultados como somente leitura, ou se estiver modificando o Bufferconteúdo da entrada , poderá evitar cópias desnecessárias da memória.

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
Константин Ван
fonte
4
Está tudo muito bem ... mas você realmente respondeu à pergunta da OP? Se você fez, é enterrado ...
Tustin2121
Ótima resposta! In obtain_arraybuffer: buf.buffer.subarrayparece não existir. Você quis dizer buf.buffer.sliceaqui?
produtivo todos os dias
@everydayproductive Obrigado. Como você pode ver no histórico de edições, eu realmente usei ArrayBuffer.prototype.slicee depois modifiquei para Uint8Array.prototype.subarray. Ah, e eu fiz errado. Provavelmente ficou um pouco confuso naquela época. Está tudo bem agora, graças a você.
Константин Ван
12

Use o seguinte pacote de npm excelente: to-arraybuffer.

Ou, você pode implementá-lo você mesmo. Se seu buffer for chamado buf, faça o seguinte:

buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
Feross
fonte
21
Joyent tirou
aleclarson
1

Você pode pensar em um ArrayBuffercomo digitado Buffer.

Um, ArrayBufferportanto, sempre precisa de um tipo (a chamada "Visualização de buffer de matriz"). Normalmente, a exibição de buffer de matriz tem um tipo de Uint8Arrayou Uint16Array.

Há um bom artigo de Renato Mangini sobre a conversão entre um ArrayBuffer e um String .

Resumi as partes essenciais em um exemplo de código (para Node.js). Também mostra como converter entre o digitado ArrayBuffere o não digitado 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
fonte
0

Eu tentei o acima para um Float64Array e ele simplesmente não funcionou.

Acabei percebendo que realmente os dados precisavam ser lidos 'NA' visualização em partes corretas. Isso significa ler 8 bytes de cada vez no buffer de origem.

Enfim, é isso que eu acabei com ...

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
fonte
É por isso que a resposta de Martin Thomson usa o Uint8Array - é independente do tamanho dos elementos. Os Buffer.read*métodos também são lentos.
ZachB
Várias visualizações de matriz digitada podem referenciar o mesmo ArrayBuffer usando a mesma memória. Cada valor em um buffer é de um byte; portanto, é necessário colocá-lo em uma matriz com tamanho de elemento de 1 byte. Você pode usar o método de Martin e criar um novo Float64Array usando o mesmo buffer de matriz no construtor.
ZachB 15/07
0

Este proxy irá expor o buffer como qualquer um dos TypedArrays, sem nenhuma cópia. :

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

Funciona apenas no LE, mas pode ser facilmente portado para o BE. Além disso, nunca chegou a testar o quão eficiente isso é.

Dlabz
fonte
1
Embora esse link possa responder à pergunta, é melhor incluir aqui as partes essenciais da resposta e fornecer o link para referência. Respostas só-Link pode se tornar inválido se as mudanças de páginas ligadas
koceeng
2
Minha redação pode não parecer muito oficial, mas fornece informações suficientes para recriar a solução. A solução depende do JavaScript Proxy Object para agrupar um buffer NodeJS nativo com getters e setters usados ​​pelo TypedArrays. Isso torna a instância do Buffer compatível com qualquer biblioteca que requer a interface Typed Array. Esta é a resposta que o autor original esperava, mas fique à vontade para descartá-lo, pois ele não se encaixa no seu idioma acadêmico / corporativo. Veja se me importo.
Dlabz
-1

O NodeJS, em um ponto (acho que era v0.6.x), tinha suporte ao ArrayBuffer. Criei uma pequena biblioteca para codificação e decodificação base64 aqui , mas desde a atualização para a v0.7, os testes (no NodeJS) falham. Estou pensando em criar algo que normalize isso, mas até então, acho que o nativo do Node Bufferdeve ser usado.

arunjitsingh
fonte
-6

Já atualizei meu nó para a versão 5.0.0 e trabalho com isso:

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
}

Eu o uso para verificar minha imagem de disco vhd.

Miguel Valentine
fonte
Parece um método especializado (e lento) baseado em serialização, não um método genérico para converter de / para Buffer / ArrayBuffer?
ZachB
@ZachB é um método genérico para V5.0.0 + [apenas] = =.
Miguel Valentine
toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- isso está retornando uma matriz de seqüências de caracteres, não números inteiros / bytes.
ZachB
@ZachB return array -> lista de retorno. i corrigir INT> string para stdout
Miguel Valentine
Nesse caso, é o mesmo que stackoverflow.com/a/19544002/1218408 e, ainda assim, sem necessidade, o deslocamento de bytes verifica em stackoverflow.com/a/31394257/1218408 .
ZachB