Перетворити двійковий буфер NodeJS в JavaScript ArrayBuffer


133

Як я можу перетворити бінарний буфер NodeJS в JavaScript ArrayBuffer?


1
Мені цікаво, навіщо вам це потрібно робити?
Кріс Біскарді

14
хорошим прикладом може бути написання бібліотеки, яка працювала з File у браузерах, а також для файлів NodeJS?
fbstj

1
або за допомогою бібліотеки браузера в NodeJS
OrangeDog

1
Ще одна причина полягає в тому, що поплавок займає занадто багато байтів оперативної пам’яті, коли він зберігається в Array. Тож для зберігання багатьох плавців вам потрібно Float32Arrayтам, де потрібно 4 байти. І якщо ви хочете швидкої серіалізації цих плаваючих файлів, вам потрібен файл Buffer, оскільки серіалізація до JSON займає багато років.
nponeccop

Я хочу знати саме те саме, що надсилати загальні дані за допомогою WebRTC, і це неймовірно, що так багато відповідей тут має стільки лайків, але не відповідають на власне питання ...
Фелікс Крацолара

Відповіді:


134

Екземпляри Bufferтакож є екземплярамиUint8Array у node.js 4.x та новіших версій. Таким чином, найефективнішим рішенням є доступ до buf.bufferресурсу безпосередньо, відповідно до https://stackoverflow.com/a/31394257/1375574 . Конструктор Buffer також приймає аргумент ArrayBufferView, якщо вам потрібно піти в іншому напрямку.

Зауважте, що це не створить копію, а це означає, що запис у будь-який ArrayBufferView буде записувати в оригінальний екземпляр Buffer.


У старих версіях node.js має як ArrayBuffer, як частину v8, але клас Buffer забезпечує більш гнучкий API. Для того, щоб читати або записувати в ArrayBuffer, вам потрібно лише створити представлення даних і скопіювати впоперек.

Від буфера до 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;
}

Від ArrayBuffer до буфера:

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
Я також рекомендую вам оптимізувати це, якщо можливо, скопіювавши цілі числа за допомогою DataView. Поки не size&0xfffffffeскопіюйте 32-бітні цілі числа, тоді, якщо залишився 1 байт, скопіюйте 8-бітове ціле число, якщо 2 байти, скопіюйте 16-бітове ціле число, а якщо 3 байти, скопіюйте 16-бітне та 8-бітове ціле число.
Triang3l

3
Дивіться відповідь kraag22 для більш простої реалізації половини цього.
OrangeDog

Тестували Buffer -> ArrayBuffer з модулем, призначеним для використання в браузері, і він працює чудово. Дякую!
pospi

3
Чому abповертається? З цим нічого не робиться ab? Я завжди отримую {}результат.
Andi Giga

1
" slice()Метод повертає нове ArrayBuffer, вміст якого є копією ArrayBufferбайтів цього кроку від початку, включно, до кінця, ексклюзиву." - MDNArrayBuffer.prototype.slice()
Константин Ван

61

Ніяких залежностей, найшвидше, Node.js 4.x та новіших версій

Buffers є Uint8Arrays, тому вам просто потрібно вирізати (скопіювати) його область підкладки 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);

Матеріали sliceта зсув необхідні, оскільки малі Buffers (за замовчуванням менше 4 кБ, половина розміру пулу ) можуть переглядати спільний доступ ArrayBuffer. Не нарізаючи, ви можете отримати ArrayBufferдані, що містять інші Buffer. Див пояснення в документах .

Якщо в кінцевому підсумку вам потрібен а TypedArray, ви можете створити його, не копіюючи дані:

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

Ніяких залежностей, помірної швидкості, будь-якої версії Node.js

Скористайтеся відповіддю Мартіна Томсона , який працює в O (n) час. (Дивіться також мої відповіді на коментарі до його відповіді про неоптимізацію. Використання DataView повільно. Навіть якщо вам потрібно перевернути байти, є більш швидкі способи зробити це.)

Залежність, швидко, Node.js ≤ 0,12 або iojs 3.x

Ви можете скористатися https://www.npmjs.com/package/memcpy, щоб перейти в будь-який бік (буфер до ArrayBuffer і назад). Це швидше, ніж інші відповіді, розміщені тут, і це добре написана бібліотека. Вузол 0,12 через iojs 3.x потребує вилки ngossen (див. Це ).


Він не компілює знову вузол> 0,12
Павло Веселов

1
Використовуйте вилку ngossen : github.com/dcodeIO/node-memcpy/pull/6 . Дивіться також мою нову відповідь, якщо ви використовуєте вузол 4+.
ZachB

Де були .byteLengthта .byteOffsetзадокументовані документи?
Константин Ван

1
@ K._ Ці властивості успадковуються від TypedArray: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… та developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
ZachB

1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);врятував мій день
Олексій Ш.

56

"Від ArrayBuffer до буфера" можна зробити так:

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

27
Це протилежне тому, чого хотів ОП.
Олександр Гончій

43
Але це те, що я хотів, гуглюючи свою проблему, і радий, що знайшов рішення.
Maciej Krawczyk

27

Швидший спосіб її написати

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

Однак, схоже, це працює приблизно в 4 рази повільніше, ніж запропонована функція toArrayBuffer на буфері з 1024 елементами.


3
Пізнє додавання: @trevnorris каже: "починаючи з [V8] 4.3 Буфери підтримуються Uint8Array", тому, можливо, зараз це швидше ...
ChrisV

Дивіться мою відповідь щодо безпечного способу цього зробити.
ZachB

3
Тестував його на v5.6.0, і це було найшвидше
daksh_019

1
Це працює лише тому, що екземпляри Bufferтакож є екземплярами Uint8Arrayв Node.js 4.x і вище. Для нижчих версій Node.js ви повинні реалізувати toArrayBufferфункцію.
Бенні Нойгебауер

14

1. A Buffer- це лише погляд на пошук ArrayBuffer.

Buffer, По суті, являє собою FastBuffer, який extends(успадковує від) Uint8Array, який представляє собою октет-блок вид ( «частковий аксессор») від фактичної пам'яті, з 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. Розмір ArrayBufferі розмір його виду можуть змінюватися.

Причина № 1: Buffer.from(arrayBuffer[, byteOffset[, length]]).

За допомогою цього пункту Buffer.from(arrayBuffer[, byteOffset[, length]])ви можете створити за Bufferдопомогою вказівки його основи ArrayBufferта положення та розміру подання.

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.

Причина №2: FastBufferвиділення пам'яті.

Він розподіляє пам'ять двома різними способами залежно від розміру.

  • Якщо розмір менше половини розміру пулу пам'яті і не дорівнює 0 ("малий") : для підготовки потрібної пам'яті використовується пул пам'яті.
  • Інше : він створює виділений, ArrayBufferякий точно відповідає необхідній пам'яті.
  📜 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));
}

Що ви розумієте під " пулом пам'яті "?

Пул пам'яті є фіксованим розміром попередньо виділено блоком пам'яті для зберігання шматків пам'яті малого розміру для Bufferс. Використовуючи його, утримуйте шматки пам’яті невеликого розміру щільно разом, тому запобігає фрагментації, спричиненій окремим управлінням (розподілом та розподілом) невеликих розмірів пам’яті.

У цьому випадку пули пам’яті ArrayBuffers, розмір яких за замовчуванням дорівнює 8 KiB, що вказано в Buffer.poolSize. Коли потрібно забезпечити невеликий фрагмент пам’яті для a Buffer, він перевіряє, чи є в останнього пулу пам’яті достатньо пам’яті, щоб обробити це; якщо так, то він створює , Bufferщо «вид» даний частковий шматок пулу пам'яті, в іншому випадку, він створює новий пул пам'яті і так далі.


Ви можете отримати доступ до основних ArrayBufferз Buffer. В Buffer«S bufferвластивість (тобто, успадковане від Uint8Array) утримує його. «Невеликий» «s властивість є , який представляє весь пул пам'яті. Так що в цьому випадку величина та і змінюється в розмірах. 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. Отже, нам потрібно витягти пам'ять, яку вона « переглядає ».

ArrayBufferФіксується в розмірах, так що ми повинні витягти його, зробивши копію частини. Для цього ми використовуємо Buffer«s byteOffsetмайно і lengthмайно , які успадковуються від Uint8Arrayі на ArrayBuffer.prototype.sliceметод , який робить копію частини ArrayBuffer. Метод slice()-ing тут був натхненний @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. Поліпшення продуктивності

Якщо ви використовуєте результати як лише для читання, або не вдається змінити Bufferвміст вхідних даних , ви можете уникнути зайвого копіювання пам'яті.

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
Це все добре і добре ... але ви насправді відповідали на питання ОП? Якщо ви це зробили, його поховали ...
Tustin2121

Чудова відповідь! В obtain_arraybuffer: buf.buffer.subarrayСхоже, не існує. Ви buf.buffer.sliceтут мали на увазі ?
щоденний продуктивний

@everydayproductive Дякую Як ви бачите в історії редагування, я фактично використовував ArrayBuffer.prototype.sliceі пізніше модифікував її Uint8Array.prototype.subarray. О, і я зробив це неправильно. Напевно, тоді трохи заплутався. Зараз все добре завдяки вам.
Константин Ван

12

Використовуйте наступний чудовий пакет NPM: to-arraybuffer.

Або ви можете виконати це самостійно. Якщо ваш буфер викликається buf, зробіть це:

buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)


1

Ви можете думати про ArrayBufferтипізований Buffer.

ArrayBufferТому завжди потрібен тип (так званий «Array Buffer View»). Як правило, перегляд буфера масиву має тип Uint8Arrayабо Uint16Array.

Є хороша стаття Ренато Мангіні про перетворення між ArrayBuffer і String .

Я узагальнив основні частини в прикладі коду (для Node.js). Він також показує, як конвертувати між введеним ArrayBufferі типовим 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

Я випробував вище для Float64Array, і це просто не вийшло.

Я зрозумів, що насправді дані потрібно читати "INTO" подання правильними шматками. Це означає читати 8 байтів одночасно з вихідного буфера.

У всякому разі, це те, що я закінчив ...

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

Ось чому у відповіді Мартіна Томсона використовується Uint8Array - це агностично щодо розміру елементів. Усі Buffer.read*методи також повільні.
ZachB

Кілька типів представлених масивів можуть посилатися на той самий ArrayBuffer, використовуючи ту саму пам'ять. Кожне значення буфера є одним байтом, тому його потрібно помістити в масив з розміром елемента в 1 байт. Ви можете скористатися методом Мартіна, а потім зробити новий Float64Array, використовуючи той самий масив буфера в конструкторі.
ZachB

0

Цей проксі відкриє буфер як будь-який з TypedArrays, без жодної копії. :

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

Він працює лише на LE, але його можна легко перенести на BE. Також ніколи насправді не перевіряли, наскільки це ефективно.


1
Хоча це посилання може відповісти на питання, краще включити сюди суттєві частини відповіді та надати посилання для довідки. Відповіді лише на посилання можуть стати недійсними, якщо
змінна

2
Моє формулювання може здатися не дуже офіційним, але воно дає достатню інформацію для відтворення рішення. Рішення покладається на JavaScript-проксі-об’єкт, щоб обернути нативний буфер NodeJS за допомогою геттерів та сетерів, що використовуються TypedArrays. Це робить екземпляр Buffer сумісним з будь-якою бібліотекою, для якої потрібен інтерфейс Typed Array. На цю відповідь сподівався оригінальний плакат, але сміливо відхиляйте його, оскільки він не відповідає вашому академічному / корпоративному мові. Подивіться, чи мені байдуже.
Длабз

0

Тепер для цього є дуже корисний пакет npm: buffer https://github.com/feross/buffer

Він намагається надати API, який на 100% ідентичний буферному API API та дозволяє:

і ще кілька.


-1

У один момент NodeJS (я думаю, це було v0.6.x) мав підтримку ArrayBuffer. Я створив невелику бібліотеку для base64 кодування і декодування тут , але так як оновлення до v0.7, тести (на NodeJS) зазнають невдачі. Я думаю створити щось, що це нормалізує, але до цього часу, мабуть, Bufferслід використовувати рідну Node .


-6

Я вже оновив свій вузол до версії 5.0.0, і я працюю над цим:

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
}

Я використовую його для перевірки зображення на диску VHD.


Це виглядає як спеціалізований (і повільний) метод на основі серіалізації, а не загальний метод перетворення в / з буфера / ArrayBuffer?
ZachB

@ZachB - це загальний метод для V5.0.0 + [тільки] = =.
Мігель Валентин

toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- це повернення масиву рядків, а не цілих чисел / байтів.
ZachB

@ZachB return масив -> список повернення. я виправляю int-> рядок для stdout
Мігель Валентин

У цьому випадку це те саме, що stackoverflow.com/a/19544002/1218408 , і все ще без необхідних перевірок зміщення байтів у stackoverflow.com/a/31394257/1218408 .
ZachB
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.