将二进制NodeJS缓冲区转换为JavaScript ArrayBuffer


133

如何将NodeJS二进制缓冲区转换为JavaScript ArrayBuffer?


1
我很好奇您为什么需要这样做?
克里斯·比斯卡迪

14
一个很好的例子是编写一个在浏览器中使用File's的库,以及NodeJS文件?
fbstj 2012年

1
或在
NodeJS中

1
另一个原因是,浮点数存储在时会占用太多字节的RAM Array。因此,要存储许多浮点数,您需要Float32Array占用4个字节的位置。而且,如果要将这些浮动对象快速序列化到文件中,则需要一个Buffer,因为序列化为JSON需要很长时间。
nponeccop

我想知道使用WebRTC发送通用数据的完全一样的东西,令人难以置信的是,这里有这么多答案有很多喜欢,但没有回答实际问题……
Felix Crazzolara

Answers:


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,您只需要创建一个视图并复制即可。

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

从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 2014年

3
为什么要ab退货?有什么不干的ab?我总是得到{}结果。
安迪·吉加

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和偏移的东西是需要,因为小Buffer秒(小于4kB的默认状态下,一半的池大小)可以是在共享视图ArrayBuffer。如果不进行切片,最终可能ArrayBuffer包含来自another的数据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

使用Martin Thomson的答案,该答案运行时间为O(n)。(另请参阅我对他关于非优化问题的回答的评论。使用DataView速度很慢。即使需要翻转字节,也有更快的方法。)

快速的依赖关系,Node.js≤0.12或iojs 3.x

您可以使用https://www.npmjs.com/package/memcpy沿任一方向运行(缓冲区到ArrayBuffer并返回)。它比此处发布的其他答案更快,并且是一个编写良好的库。节点通过0.12 3.X iojs需要ngossen的叉(见)。


它不会再次编译> 0.12的节点
Pawel Veselov

1
使用ngossen的fork:github.com/dcodeIO/node-memcpy/pull/6。如果您使用的是节点4+,另请参阅我的新答案。
ZachB

.byteLength.byteOffset记录在哪里?
КонстантинВан


1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);拯救了我的一天
Alexey Sh。

55

可以通过以下方式完成“从ArrayBuffer到Buffer”:

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

27
这与OP想要的相反。
亚历山大·贡奇

42
但是,这就是我要查找问题并很高兴找到解决方案的原因。
Maciej Krawczyk

27

一种更快的书写方式

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

但是,在具有1024个元素的缓冲区上,这似乎比建议的toArrayBuffer函数慢大约4倍。


3
后期添加:@trevnorris “从[V8] 4.3开始,缓冲区由Uint8Array支持”,所以现在这可能会更快...
ChrisV

请参阅我的答案以获取安全的方法。
ZachB

3
使用v5.6.0测试了它,它是最快的
daksh_019 '16

1
这只是工作,因为实例Buffer也的实例Uint8Array中的Node.js 4.x和更高。对于较低的Node.js版本,您必须实现一个toArrayBuffer功能。
Benny Neugebauer

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 KiB(在中指定)Buffer.poolSize。当要为提供一个较小的内存块时Buffer,它将检查最后一个内存池是否有足够的可用内存来处理此内存;如果是的话,它会创建一个Buffer“意见”的内存池的给定的部分块,否则,将创建一个新的内存池等。


您可以访问底层ArrayBufferBuffer。该Bufferbuffer财产(即从继承Uint8Array)持有它。一个“小” Bufferbuffer属性是ArrayBuffer代表整个内存池。因此,在这种情况下,ArrayBuffer和的Buffer大小会有所不同。

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.因此,我们需要将内存提取为“ views”

ArrayBuffer大小是固定的,因此我们需要通过复制零件来将其提取出来。要做到这一点,我们使用BufferbyteOffset财产length财产,这是从继承Uint8Array,并且ArrayBuffer.prototype.slice方法,这使得一个部分的副本ArrayBufferslice()本文中的-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
一切都很好...但是您实际上回答了OP的问题吗?如果您这样做了,它就被掩埋了……
Tustin2121 '18

好答案!在中obtain_arraybufferbuf.buffer.subarray似乎不存在。你的意思是buf.buffer.slice这里吗?
每天高效的

@everydayproductive谢谢。如您在编辑历史记录中看到的,我实际上使用了ArrayBuffer.prototype.slice它,后来将其修改为Uint8Array.prototype.subarray。哦,我做错了。那时可能有点困惑。谢谢,现在一切都很好。
КонстантинВан


1

您可以将an ArrayBuffer视为typed Buffer

一个ArrayBuffer因此总是需要一个类型(即所谓“数组缓冲区视图”)。通常,数组缓冲区视图的类型为Uint8ArrayUint16Array

Renato Mangini有一篇很好的文章介绍了如何在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上尝试了上面的方法,但是它没有用。

我最终意识到,实际上需要按正确的块将数据“读入”视图。这意味着一次从源缓冲区读取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++;
}

这就是Martin Thomson的答案使用Uint8Array的原因-它与元素的大小无关。该Buffer.read*方法都是缓慢的,也。
ZachB

多个类型化的数组视图可以使用相同的内存引用相同的ArrayBuffer。缓冲区中的每个值都是一个字节,因此您需要将其放入元素大小为1字节的数组中。您可以使用Martin的方法,然后在构造函数中使用相同的arraybuffer制作一个新的Float64Array。
ZachB

0

该代理将缓冲区公开为TypedArrays中的任何一个,没有任何副本。:

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

它仅适用于LE,但可以轻松移植到BE。同样,永远也不必实际测试这种方法的效率。


1
尽管此链接可以回答问题,但最好在此处包括答案的基本部分,并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效
koceeng '17

2
我的措词听起来可能不太正式,但确实提供了足够的信息来重新创建解决方案。该解决方案依靠JavaScript Proxy Object来包装带有TypedArrays使用的getter和setter的本地NodeJS Buffer。这使Buffer实例与任何需要Typed Array接口的库兼容。这是原始海报所希望的答案,但是请放心,因为它不适合您的学术/企业术语。看看我是否在乎。
Dlabz


-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磁盘映像。


这看起来像是一种专门的(且缓慢的)基于序列化的方法,而不是用于与Buffer / ArrayBuffer进行相互转换的通用方法?
ZachB 2015年

@ZachB是V5.0.0 + [only] = =的通用方法。
米格尔·瓦伦丁

toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']-这将返回一个字符串数组,而不是整数/字节。
ZachB 2015年

@ZachB返回数组->返回列表。我修复了stdout的int-> string
Miguel Valentine

在这种情况下,它与stackoverflow.com/a/19544002/1218408相同,并且仍然不需要在stackoverflow.com/a/31394257/1218408中进行字节偏移检查。
ZachB
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.