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

IT技术 javascript node.js binary buffer arraybuffer
2021-01-16 11:39:31

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

6个回答

的实例Buffer也是Uint8Arraynode.js 4.x 及更高版本中的实例因此,最有效的解决方案是buf.buffer直接访问该属性,如https://stackoverflow.com/a/31394257/1375574如果您需要向另一个方向移动,则 Buffer 构造函数也接受一个 ArrayBufferView 参数。

请注意,这不会创建副本,这意味着写入任何 ArrayBufferView 都将写入原始 Buffer 实例。


在旧版本中,node.js 将 ArrayBuffer 作为 v8 的一部分,但 Buffer 类提供了更灵活的 API。为了读取或写入 ArrayBuffer,您只需要创建一个视图并进行复制。

从缓冲区到数组缓冲区:

function toArrayBuffer(buf) {
    const ab = new ArrayBuffer(buf.length);
    const view = new Uint8Array(ab);
    for (let i = 0; i < buf.length; ++i) {
        view[i] = buf[i];
    }
    return ab;
}

从 ArrayBuffer 到 Buffer:

function toBuffer(ab) {
    const buf = Buffer.alloc(ab.byteLength);
    const view = new Uint8Array(ab);
    for (let i = 0; i < buf.length; ++i) {
        buf[i] = view[i];
    }
    return buf;
}
我还建议您在可能的情况下使用 DataView 通过复制整数来优化它。直到size&0xfffffffe,复制 32 位整数,然后,如果剩余 1 个字节,则复制 8 位整数,如果是 2 个字节,则复制 16 位整数,如果是 3 个字节,则复制 16 位和 8 位整数。
2021-03-15 11:39:31
'该slice()方法返回一个新的,ArrayBuffer其内容是 this ArrayBuffer'从开始、包含、到结束、独占的字节的副本- MDNArrayBuffer.prototype.slice()
2021-03-18 11:39:31
为什么被ab退回?什么都没做ab我总是得到{}结果。
2021-03-21 11:39:31
有关其中一半的更简单实现,请参阅 kraag22 的答案。
2021-03-31 11:39:31
已经用一个供浏览器使用的module测试了 Buffer -> ArrayBuffer 并且它工作得很好。谢谢!
2021-03-31 11:39:31

无依赖,最快,Node.js 4.x 及更高版本

Buffers 是Uint8Arrays,所以你只需要切片(复制)它的 backing 区域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包含来自另一个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向任一方向前进(从 Buffer 到 ArrayBuffer 再返回)。它比此处发布的其他答案更快,并且是一个编写良好的库。Node 0.12 到 iojs 3.x 需要 ngossen 的 fork(请参阅)。

使用 ngossen 的分支:github.com/dcodeIO/node-memcpy/pull/6如果您使用的是节点 4+,另请参阅我的新答案。
2021-03-18 11:39:31
它不会再次编译 node > 0.12
2021-03-30 11:39:31
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength); 拯救了我的一天
2021-04-03 11:39:31
.byteLength.byteOffset记录在哪里
2021-04-05 11:39:31

“从 ArrayBuffer 到 Buffer”可以这样完成:

var buffer = Buffer.from( new Uint8Array(ab) );
这与 OP 想要的相反。
2021-03-13 11:39:31
但这就是我想用谷歌搜索我的问题,很高兴我找到了解决方案。
2021-04-09 11:39:31

一种更快的书写方式

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

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

后期补充:@trevnorris“从 [V8] 4.3 开始,缓冲区由 Uint8Array 支持”,所以现在可能更快......
2021-03-23 11:39:31
用v5.6.0测试过,是最快的
2021-03-26 11:39:31
这只有效,因为 的实例Buffer也是Uint8ArrayNode.js 4.x 及更高版本中的实例对于较低的 Node.js 版本,您必须实现一个toArrayBuffer功能。
2021-03-28 11:39:31
有关执行此操作的安全方法,请参阅我的答案。
2021-04-06 11:39:31

1.一种Buffer仅仅是一个视图用于寻找到的ArrayBuffer

A 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. anArrayBuffer的大小及其视图的大小可能会有所不同。

理由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“意见”的内存池的给定的部分块,否则,将创建一个新的内存池等。


您可以访问底层ArrayBufferBufferBufferbuffer财产(即从继承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. 所以我们需要提取它“查看的内存

An 的ArrayBuffer大小是固定的,因此我们需要通过制作零件的副本来提取它。要做到这一点,我们使用BufferbyteOffset财产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. 性能提升

如果您要将结果用作只读,或者可以修改输入Buffers 的内容,则可以避免不必要的内存复制。

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
这一切都很好……但你真的回答了 OP 的问题吗?如果你这样做了,它就被埋葬了......
2021-03-15 11:39:31
很好的答案!obtain_arraybufferbuf.buffer.subarray似乎不存在。你是说buf.buffer.slice这里吗?
2021-03-15 11:39:31
@everydayproductive 谢谢。正如你在编辑历史中看到的,我实际使用过ArrayBuffer.prototype.slice,后来修改为Uint8Array.prototype.subarray. 哦,我做错了。当时可能有点糊涂了。多亏了你,现在一切都很好。
2021-03-19 11:39:31