Преобразовать двоичный буфер NodeJS в JavaScript ArrayBuffer

133

Как я могу преобразовать двоичный буфер NodeJS в JavaScript ArrayBuffer?

Дрейк Амара
источник
1
Мне любопытно, зачем вам это нужно?
Крис Бискарди,
14
хорошим примером было бы написание библиотеки, которая работала бы с File в браузерах, а также для файлов NodeJS?
fbstj
1
или используя библиотеку браузера в NodeJS
OrangeDog
1
Другая причина в том, что float занимает слишком много байтов ОЗУ при хранении в Array. Итак, чтобы хранить много чисел с плавающей запятой, вам нужно Float32Array4 байта. И если вам нужна быстрая сериализация этих поплавков в файл, который вам нужен 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 к 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;
}
Мартин Томсон
источник
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байтов this от начала, включительно, до конца, исключая». - 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 (см. Это ).

ZachB
источник
Он не компилируется снова, узел> 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/…
ЗакБ
1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);спас мой день
Алексей Ш.
56

«От ArrayBuffer до Buffer» можно сделать так:

var buffer = Buffer.from( new Uint8Array(ab) );
kraag22
источник
27
Это противоположно тому, что хотел ОП.
Александр Гончий
43
Но это то, что я хотел найти в своей проблеме, и рад, что нашел решение.
Мацей Кравчик
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. А 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с. Его использование обеспечивает плотное соединение небольших блоков памяти, что предотвращает фрагментацию. вызванную раздельным управлением (выделением и освобождением) блоков памяти малого размера.

В этом случае используются пулы памяти ArrayBufferс размером по умолчанию 8 КиБ, который указан в 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«ю.ш.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)
Feross
источник
1

Вы можете думать ArrayBufferкак о набранных Buffer.

ArrayBufferПоэтому всегда нужен тип (так называемый «Array Buffer View»). Как правило, 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++;
}
Exitos
источник
Вот почему в ответе Мартина Томсона используется Uint8Array - он не зависит от размера элементов. Эти Buffer.read*методы все медленно, также.
ЗакБ
Представления нескольких типизированных массивов могут ссылаться на один и тот же ArrayBuffer, используя одну и ту же память. Каждое значение в буфере составляет один байт, поэтому вам нужно поместить его в массив с размером элемента 1 байт. Вы можете использовать метод Мартина, а затем создать новый Float64Array, используя тот же массив буферов в конструкторе.
ЗакБ
0

Этот прокси будет представлять буфер как любой из TypedArrays, без какой-либо копии. :

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

Он работает только на LE, но может быть легко перенесен на BE. Кроме того, никогда не проверял, насколько это эффективно.

Dlabz
источник
1
Хотя эта ссылка может дать ответ на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы,
содержащие
2
Моя формулировка может показаться не очень официальной, но она содержит достаточно информации, чтобы воссоздать решение. Решение использует JavaScript Proxy Object, чтобы обернуть собственный NodeJS-буфер в методы получения и установки, используемые TypedArrays. Это делает экземпляр Buffer совместимым с любой библиотекой, для которой требуется интерфейс Typed Array. Это ответ, на который надеялся оригинальный автор, но не стесняйтесь отклонить его, поскольку он не соответствует вашему академическому / корпоративному языку. Посмотри, не волнует ли меня.
Длабз
0

Теперь для этого есть очень полезный пакет npm: buffer https://github.com/feross/buffer

Он пытается предоставить API, который на 100% идентичен Buffer API узла и позволяет:

и еще немного.

Гопалакришна Палем
источник
-1

NodeJS, в какой-то момент (я думаю, это был v0.6.x) была поддержка ArrayBuffer. Я создал небольшую библиотеку для base64 кодирования и декодирования здесь , но так как обновление до v0.7, тесты (на NodeJS) терпят неудачу. Я думаю о создании чего-то, что нормализует это, но до тех пор, я полагаю, Bufferдолжен использоваться нативный нод.

arunjitsingh
источник
-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-диска.

Мигель Валентин
источник
Это похоже на специализированный (и медленный) метод на основе сериализации, а не универсальный метод для преобразования в / из Buffer / ArrayBuffer?
ZachB
@ZachB это универсальный метод для V5.0.0 + [только] = =.
Мигель Валентин
toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- это возвращает массив строк, а не целые числа / байты.
ЗакБ
Возвращаемый массив @ZachB -> список возврата. я исправляю int-> string для stdout
Мигель Валентин
В этом случае это то же самое, что и stackoverflow.com/a/19544002/1218408 , и все еще без необходимых проверок смещения байтов в stackoverflow.com/a/31394257/1218408 .
ЗакБ