Przekonwertuj binarny bufor NodeJS na JavaScript ArrayBuffer

136

Jak przekonwertować bufor binarny NodeJS na JavaScript ArrayBuffer?

Drake Amara
źródło
1
Jestem ciekaw, dlaczego miałbyś to zrobić?
Chris Biscardi,
15
dobrym przykładem byłoby napisanie biblioteki, która działałaby z plikami w przeglądarkach, a także z plikami NodeJS?
fbstj
1
lub używając biblioteki przeglądarki w NodeJS
OrangeDog,
1
Innym powodem jest to, że zmiennoprzecinkowa zajmuje zbyt wiele bajtów pamięci RAM, gdy jest przechowywana w pliku Array. Tak więc, aby zapisać wiele zmiennych, potrzebujesz Float32Array4 bajty. A jeśli chcesz szybko serializować te elementy zmiennoprzecinkowe do pliku, potrzebujesz pliku Buffer, ponieważ serializacja do formatu JSON zajmuje wieki.
nponeccop
Chcę wiedzieć dokładnie to samo, jak wysyłać dane ogólne za pomocą WebRTC i to niewiarygodne, że tak wiele odpowiedzi ma tak wiele polubień, ale nie odpowiadam na rzeczywiste pytanie ...
Felix Crazzolara

Odpowiedzi:

141

Instancje Buffersą również instancjamiUint8Array w node.js 4.xi nowszych. Dlatego najbardziej wydajnym rozwiązaniem jest buf.bufferbezpośredni dostęp do nieruchomości, zgodnie z https://stackoverflow.com/a/31394257/1375574 . Konstruktor Buffer przyjmuje również argument ArrayBufferView, jeśli chcesz pójść w innym kierunku.

Zauważ, że nie utworzy to kopii, co oznacza, że ​​zapis do dowolnego ArrayBufferView będzie zapisywać do oryginalnej instancji Buffer.


W starszych wersjach node.js zawiera zarówno ArrayBuffer jako część v8, ale klasa Buffer zapewnia bardziej elastyczne API. Aby czytać lub zapisywać w ArrayBuffer, wystarczy utworzyć widok i skopiować.

Od bufora do 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;
}

Od ArrayBuffer do 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
źródło
5
Poleciłbym również zoptymalizować to przez kopiowanie liczb całkowitych, jeśli to możliwe, przy użyciu DataView. Aż do size&0xfffffffeskopiowania 32-bitowych liczb całkowitych, a następnie, jeśli pozostał 1 bajt, skopiuj 8-bitową liczbę całkowitą, jeśli 2 bajty, skopiuj 16-bitową liczbę całkowitą, a jeśli 3 bajty, skopiuj 16-bitową i 8-bitową liczbę całkowitą.
Triang3l
3
Zobacz odpowiedź kraag22 na prostszą implementację połowy tego.
OrangeDog
Przetestowałem Buffer -> ArrayBuffer z modułem przeznaczonym do obsługi przeglądarki i działa znakomicie. Dzięki!
pospi
3
Dlaczego abwraca? Nic się nie stało ab? W {}rezultacie zawsze otrzymuję .
Andi Giga
1
slice()Metoda zwraca nowy, ArrayBufferktórego zawartość jest kopią tego ArrayBufferbajtów od początku włącznie, do końca, z wyłączeniem”. - MDNArrayBuffer.prototype.slice()
Константин Ван
66

Bez zależności, najszybszy, Node.js 4.xi nowszy

Buffers to Uint8Arrays, więc wystarczy wyciąć (skopiować) jego region podkładu 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);

Elementy slicei offset są wymagane, ponieważ małe pliki Buffer(domyślnie mniejsze niż 4 kB, połowa rozmiaru puli ) mogą być widokami na udostępnionym ArrayBuffer. Bez dzielenia na plasterki możesz otrzymać ArrayBufferdane z innego Buffer. Zobacz wyjaśnienie w dokumentacji .

Jeśli ostatecznie potrzebujesz pliku TypedArray, możesz go utworzyć bez kopiowania danych:

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

Brak zależności, umiarkowana prędkość, dowolna wersja Node.js.

Użyj odpowiedzi Martina Thomsona , która działa w czasie O (n) . (Zobacz także moje odpowiedzi na komentarze dotyczące jego odpowiedzi na temat braku optymalizacji. Korzystanie z DataView jest powolne. Nawet jeśli musisz przerzucić bajty, są na to szybsze sposoby).

Zależność, szybki, Node.js ≤ 0,12 lub iojs 3.x

Możesz użyć https://www.npmjs.com/package/memcpy, aby przejść w dowolnym kierunku (Buffer to ArrayBuffer iz powrotem). Jest szybszy niż inne zamieszczone tutaj odpowiedzi i jest dobrze napisaną biblioteką. Węzły od 0.12 do iojs 3.x wymagają rozwidlenia ngossen (zobacz to ).

ZachB
źródło
Nie kompiluje się ponownie węzeł> 0.12
Pawel Veselov
1
Użyj widelca ngossen : github.com/dcodeIO/node-memcpy/pull/6 . Zobacz także moją nową odpowiedź, jeśli używasz węzła 4+.
ZachB
Gdzie były .byteLengthi .byteOffsetudokumentowane?
Константин Ван
1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);uratował mi dzień
Alexey Sh.
57

„From ArrayBuffer to Buffer” można zrobić w ten sposób:

var buffer = Buffer.from( new Uint8Array(ab) );
kraag22
źródło
27
To przeciwieństwo tego, czego chciał OP.
Alexander Gonchiy,
44
Ale właśnie tego chciałem znaleźć w Google mój problem i cieszę się, że znalazłem rozwiązanie.
Maciej Krawczyk
28

Szybszy sposób na napisanie tego

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

Wydaje się jednak, że działa to około 4 razy wolniej niż sugerowana funkcja toArrayBuffer na buforze z 1024 elementami.

David Fooks
źródło
3
Późny dodatek: @trevnorris mówi „począwszy od [V8] 4.3 Bufory są obsługiwane przez Uint8Array”, więc prawdopodobnie jest to teraz szybsze ...
ChrisV
Zobacz moją odpowiedź, jak to zrobić w bezpieczny sposób.
ZachB
3
Przetestowałem go z wersją 5.6.0 i był najszybszy
daksh_019
1
Działa to tylko dlatego, że instancje Buffersą również instancjami Uint8Arrayw Node.js 4.xi nowszych. W przypadku niższych wersji Node.js musisz zaimplementować toArrayBufferfunkcję.
Benny Neugebauer
14

1. A Bufferto tylko widok do spojrzenia na plik ArrayBuffer.

A Bufferw rzeczywistości jest a FastBuffer, który extends(dziedziczy po) Uint8Array, który jest widokiem jednostki oktetu („częściowy akcesor”) rzeczywistej pamięci, 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. Rozmiar ArrayBufferi rozmiar widoku mogą się różnić.

Powód # 1: Buffer.from(arrayBuffer[, byteOffset[, length]]).

Za pomocą Buffer.from(arrayBuffer[, byteOffset[, length]])można utworzyć Bufferz określeniem jego podstawy ArrayBufferoraz położenia i rozmiaru widoku.

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.

Powód 2: FastBufferalokacja pamięci.

Alokuje pamięć na dwa różne sposoby w zależności od rozmiaru.

  • Jeśli rozmiar jest mniejszy niż połowa wielkości puli pamięci i nie wynosi 0 („mały”) : wykorzystuje pulę pamięci do przygotowania wymaganej pamięci.
  • W przeciwnym razie : tworzy dedykowaną, ArrayBufferktóra dokładnie pasuje do wymaganej pamięci.
  📜 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));
}

Co masz na myśli mówiąc „ pula pamięci ”?

Puli pamięci jest stałym rozmiarze wstępnie przyznane blok pamięci do przechowywania małych rozmiarów kawałki pamięci dla Buffers. Użycie go utrzymuje małe fragmenty pamięci ściśle ze sobą, dzięki czemu zapobiega fragmentacji spowodowanej oddzielnym zarządzaniem (alokacją i zwalnianiem) małych fragmentów pamięci.

W tym przypadku pule pamięci to ArrayBuffers, których rozmiar domyślnie wynosi 8 KB, który jest określony w Buffer.poolSize. Kiedy ma zapewnić niewielki fragment pamięci dla a Buffer, sprawdza, czy ostatnia pula pamięci ma wystarczającą ilość dostępnej pamięci, aby to obsłużyć; jeśli tak, to tworzy Buffer, że „poglądy” dana częściowy klocek puli pamięci, w przeciwnym razie tworzy nową pulę pamięci i tak dalej.


Możesz uzyskać dostęp do podstawy ArrayBufferpliku Buffer. W Buffer„s buffernieruchomość (czyli odziedziczone Uint8Array) trzyma go. „Mały” jest nieruchomość jest że reprezentuje całą pulę pamięci. Więc w tym przypadku, i różnią się rozmiarem. 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. Więc musimy wyodrębnić pamięci „ widoki ”.

ArrayBufferJest ustalona w wielkości, więc trzeba wyodrębnić ją poprzez kopię strony. Aby to zrobić, używamy Buffer„s byteOffsetwłasności i lengthwłaściwości , które są dziedziczone Uint8Array, a ten ArrayBuffer.prototype.slicesposób , co sprawia, kopię część kontroli ArrayBuffer. Przedstawiona slice()tutaj metoda -ing została zainspirowana @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. Poprawa wydajności

Jeśli jesteś wykorzystanie wyników jako tylko do odczytu, czy to jest w porządku, aby zmodyfikować wprowadzić BufferS'zawartość , można uniknąć niepotrzebnego kopiowania pamięci.

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
Константин Ван
źródło
4
To wszystko dobrze i dobrze ... ale czy rzeczywiście odpowiedziałeś na pytanie OP? Jeśli tak, jest pochowany ...
Tustin2121,
Świetna odpowiedź! W obtain_arraybuffer: buf.buffer.subarraynie wydaje się istnieć. Miałeś na myśli buf.buffer.slicetutaj?
produktywny codziennie
@everydayproductive Dziękuję. Jak widać w historii edycji, użyłem go, ArrayBuffer.prototype.slicea później zmodyfikowałem na Uint8Array.prototype.subarray. Aha, i zrobiłem to źle. Prawdopodobnie był wtedy trochę zdezorientowany. Dzięki tobie wszystko jest w porządku.
Константин Ван
12

Użyj następującego doskonały pakiet npm: to-arraybuffer.

Lub możesz to zaimplementować samodzielnie. Jeśli wywoływany jest twój bufor buf, zrób to:

buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
Feross
źródło
21
Joyent go wyciągnął
aleclarson
1

Możesz myśleć o ArrayBuffertypie Buffer.

ArrayBufferDlatego zawsze potrzebuje typ (tak zwane „Array Buffer View”). Zazwyczaj widok bufora tablicy ma typ Uint8Arraylub Uint16Array.

Renato Mangini ma dobry artykuł na temat konwersji między ArrayBuffer a String .

Podsumowałem najważniejsze części w przykładzie kodu (dla Node.js). Pokazuje również, jak konwertować między typem ArrayBuffera nietypowym 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
źródło
0

Wypróbowałem powyższe dla Float64Array i po prostu nie działało.

Skończyło się na tym, że zdałem sobie sprawę, że naprawdę dane muszą być odczytywane „DO” widoku w odpowiednich fragmentach. Oznacza to jednoczesne odczytywanie 8 bajtów z bufora źródłowego.

W każdym razie na tym skończyłem ...

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
źródło
Dlatego odpowiedź Martina Thomsona wykorzystuje Uint8Array - jest on agnostyczny w stosunku do rozmiaru elementów. Wszystkie Buffer.read*metody są również powolne.
ZachB,
Wiele widoków tablic o określonym typie może odwoływać się do tego samego ArrayBuffer przy użyciu tej samej pamięci. Każda wartość w buforze to jeden bajt, więc musisz umieścić ją w tablicy o rozmiarze elementu 1 bajt. Możesz użyć metody Martina, a następnie utworzyć nową tablicę Float64Array, używając tego samego bufora tablicy w konstruktorze.
ZachB,
0

Ten serwer proxy ujawni bufor jako dowolną z TypedArrays, bez żadnej kopii. :

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

Działa tylko na LE, ale można go łatwo przenieść do BE. Ponadto nigdy nie udało mi się przetestować, jak wydajne jest to.

Dlabz
źródło
1
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli strona, do której prowadzi
łącze, ulegnie
2
Moje sformułowanie może nie brzmieć zbyt oficjalnie, ale dostarcza wystarczających informacji, aby odtworzyć rozwiązanie. Rozwiązanie opiera się na obiekcie proxy JavaScript, który opakowuje natywny bufor NodeJS w metody pobierające i ustawiające używane przez TypedArrays. Dzięki temu instancja Buffer jest kompatybilna z każdą biblioteką, która wymaga interfejsu Typed Array. To jest odpowiedź, na którą liczył oryginalny plakat, ale możesz ją odrzucić, ponieważ nie pasuje do twojego akademickiego / korporacyjnego żargonu. Zobacz czy mi zależy.
Dlabz,
0

Teraz jest do tego bardzo przydatny pakiet npm: buffer https://github.com/feross/buffer

Próbuje zapewnić interfejs API, który jest w 100% identyczny z interfejsem Buffer API węzła i umożliwia:

i kilka innych.

Gopalakrishna Palem
źródło
-1

NodeJS w pewnym momencie (myślę, że to była wersja 0.6.x) miał obsługę ArrayBuffer. Stworzyłem małą bibliotekę dla base64 kodowania i dekodowania tutaj , ale od aktualizacji do v0.7, testy (na NodeJS) niepowodzeniem. Myślę o stworzeniu czegoś, co normalizowałoby to, ale przypuszczam, że do tego czasu Bufferpowinien być używany natywny Node .

arunjitsingh
źródło
-6

Zaktualizowałem już mój węzeł do wersji 5.0.0 I pracuję z tym:

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
}

Używam go do sprawdzenia obrazu dysku vhd.

Miguel Valentine
źródło
Wygląda na to, że jest to wyspecjalizowana (i wolna) metoda oparta na serializacji, a nie ogólna metoda konwersji do / z buforu / tablicy?
ZachB
@ZachB jest to ogólna metoda dla wersji 5.0.0 + [tylko] = =.
Miguel Valentine
toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- to zwraca tablicę ciągów, a nie liczb całkowitych / bajtów.
ZachB
@ZachB tablica powrotu -> lista zwrotów. naprawiam ciąg int-> dla stdout
Miguel Valentine
W tym przypadku jest to to samo, co stackoverflow.com/a/19544002/1218408 i nadal bez potrzeby sprawdzanie przesunięcia bajtów w stackoverflow.com/a/31394257/1218408 .
ZachB