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

Posted

技术标签:

【中文标题】将二进制 NodeJS 缓冲区转换为 JavaScript ArrayBuffer【英文标题】:Convert a binary NodeJS Buffer to JavaScript ArrayBuffer 【发布时间】:2012-01-26 09:56:22 【问题描述】:

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

【问题讨论】:

我很好奇您为什么需要这样做? 一个很好的例子是编写一个与浏览器中的 File 以及 NodeJS 文件一起使用的库? 或者在NodeJS中使用浏览器库 另一个原因是浮点数在存储在Array 中时占用了太多的 RAM 字节。因此,要存储许多浮点数,您需要Float32Array,它需要 4 个字节。如果您想将这些浮点数快速序列化为文件,则需要 Buffer,因为序列化为 JSON 需要很长时间。 const file = fs.readFileSync(filePath);,那我该怎么用呢?... 30 分钟后,哇,我想念 C。 【参考方案1】:

BufferviewArrayBuffer。您可以使用buffer 属性访问内部包装的ArrayBuffer

这是共享内存,不需要复制。

const arrayBuffer = theBuffer.buffer

如果您想要数据的copy,请从原始Buffer(不是来自包装的ArrayBuffer)创建另一个Buffer,然后引用其包装的ArrayBuffer

const newArrayBuffer = Buffer.from(theBuffer).buffer

作为参考,从另一个方向,从 ArrayBufferBuffer

const arrayBuffer = getArrayBuffer()
const sharedBuffer = Buffer.from(arrayBuffer)

const copiedBuffer = Buffer.from(sharedBuffer)
const copiedArrayBuffer = copiedBuffer.buffer

【讨论】:

【参考方案2】:

Instances of Buffer are also instances of Uint8Array 在 node.js 4.x 及更高版本中。因此,最有效的解决方案是按照https://***.com/a/31394257/1375574 直接访问buf.buffer 属性。 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&amp;0xfffffffe,复制 32 位整数,然后,如果剩余 1 个字节,则复制 8 位整数,如果是 2 个字节,则复制 16 位整数,如果是 3 个字节,则复制 16 位和 8 位整数。 查看 kraag22 的答案,了解其中一半的更简单实现。 已经用一个供浏览器使用的模块测试了 Buffer -> ArrayBuffer,它运行良好。谢谢! 为什么会返回abab 什么都没做?结果我总是得到 'slice() 方法返回一个新的ArrayBuffer,其内容是此ArrayBuffer 从开始、包含、到结束、不包含的字节的副本。' - MDN ArrayBuffer.prototype.slice()【参考方案3】:

无依赖,最快,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 kB,pool size 的一半)可以在共享的 ArrayBuffer 上查看。如果不进行切片,您最终可能会得到一个包含来自另一个 Buffer 的数据的 ArrayBuffer。见explanation in the docs。

如果您最终需要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's answer,它在O(n) 时间内运行。 (另请参阅我对 cme​​ts 关于非优化的回答。使用 DataView 很慢。即使您需要翻转字节,也有更快的方法。)

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

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

【讨论】:

不再编译 node > 0.12 使用 ngossen 的 fork:github.com/dcodeIO/node-memcpy/pull/6。如果您使用的是节点 4+,另请参阅我的新答案。 .byteLength.byteOffset 记录在哪里? @K._ 这些属性继承自 TypedArray:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 和 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength); 拯救了我的一天【参考方案4】:

现在有一个非常有用的 npm 包:bufferhttps://github.com/feross/buffer

它试图提供一个与节点的 Buffer API 100% 相同的 API 并允许:

将类型化数组转换为缓冲区:https://github.com/feross/buffer#convert-typed-array-to-buffer 将缓冲区转换为类型化数组:https://github.com/feross/buffer#convert-buffer-to-typed-array

还有更多。

【讨论】:

【参考方案5】:

1。 Buffer 只是查看ArrayBuffer视图

一个Buffer,其实就是一个FastBuffer,其中extends(继承自)Uint8Array,是一个八位字节单元view(“partial accessor”)实际内存,ArrayBuffer

?/lib/buffer.js#L65-L73 Node.js 9.4.0
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(“小”):它使用 内存池 准备所需的内存。 Else:它会创建一个专用的ArrayBuffer,完全适合所需的内存。 ?/lib/buffer.js#L306-L320 Node.js 9.4.0
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);
  

?/lib/buffer.js#L98-L100 Node.js 9.4.0
function createUnsafeBuffer(size) 
  return new FastBuffer(createUnsafeArrayBuffer(size));


内存池”是什么意思?

memory pool 是一个固定大小的预分配内存块,用于为Buffers 保留小尺寸内存块。使用它可以将小内存块紧密地结合在一起,从而防止fragmentation 由小内存块的单独管理(分配和释放)引起。

在这种情况下,内存池为ArrayBuffers,默认大小为8 KiB,在Buffer.poolSize中指定。当它为Buffer 提供一个小内存块时,它会检查最后一个内存池是否有足够的可用内存来处理这个问题;如果是,它会创建一个Buffer“查看”内存池的给定部分块,否则,它会创建一个新的内存池,依此类推。


您可以访问Buffer 的底层ArrayBufferBufferbuffer property(即继承自Uint8Array)持有它。 一个“小”Bufferbuffer 属性是一个代表整个内存池的ArrayBuffer所以在这种情况下,ArrayBufferBuffer 大小不一。

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 property 和length property,它们继承自Uint8Array 和the ArrayBuffer.prototype.slice method,它复制了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的问题吗?如果你这样做了,它就被掩埋了…… 很好的答案!在obtain_arraybufferbuf.buffer.subarray 似乎不存在。你的意思是buf.buffer.slice 吗? @everydayproductive 谢谢。在编辑历史中可以看到,我实际上使用了ArrayBuffer.prototype.slice,后来修改为Uint8Array.prototype.subarray。哦,我做错了。那时大概是有点迷茫了。多亏了你,现在一切都好。【参考方案6】:

"From ArrayBuffer to Buffer"可以这样实现:

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

【讨论】:

这与 OP 想要的相反。 但这就是我想用谷歌搜索我的问题,很高兴我找到了解决方案。【参考方案7】:

您可以将ArrayBuffer 视为键入的Buffer

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

Renato Mangini 在converting between an ArrayBuffer and a 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!"

【讨论】:

【参考方案8】:

这个 Proxy 会将缓冲区作为任何 TypedArrays 公开,没有任何副本。 :

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

它仅适用于 LE,但可以轻松移植到 BE。 此外,从来没有真正测试过它的效率。

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效 我的措辞可能听起来不是很正式,但它确实提供了足够的信息来重新创建解决方案。该解决方案依赖 JavaScript 代理对象来包装原生 NodeJS 缓冲区,其中包含 TypedArrays 使用的 getter 和 setter。这使得 Buffer 实例与任何需要 Typed Array 接口的库兼容。这是原始发帖人希望得到的答案,但请随意忽略它,因为它不符合您的学术/企业术语。看看我是否在乎。【参考方案9】:

使用以下优秀的 npm 包:to-arraybuffer

或者,您可以自己实现它。如果您的缓冲区名为buf,请执行以下操作:

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

【讨论】:

Joyent took it out【参考方案10】:

我已经将我的节点更新到版本 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 它是 V5.0.0+[only] 的通用方法 = =。 toArrayBuffer(new Buffer([1,2,3])) -> ['01', '02', '03'] -- 返回一个字符串数组,而不是整数/字节。 @ZachB 返回数组->返回列表。我修复了标准输出的 int->string 在这种情况下,它与***.com/a/19544002/1218408 相同,并且仍然不需要在***.com/a/31394257/1218408 中进行字节偏移检查。【参考方案11】:

我对 Float64Array 尝试了上述方法,但它不起作用。

我最终意识到,确实需要以正确的块“读取”视图中的数据。这意味着一次从源 Buffer 中读取 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* 方法也都很慢。 多个类型的数组视图可以使用相同的内存引用相同的 ArrayBuffer。 Buffer 中的每个值都是一个字节,因此您需要将其放入元素大小为 1 字节的数组中。您可以使用 Martin 的方法,然后在构造函数中使用相同的 arraybuffer 创建一个新的 Float64Array。【参考方案12】:

一种更快捷的编写方式

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

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

【讨论】:

后期添加:@trevnorris says "starting in [V8] 4.3 Buffers are backed by Uint8Array",所以现在可能更快... 查看我的答案以了解安全的方法。 用v5.6.0测试过,最快 这只是因为 Buffer 的实例也是 Node.js 4.x 及更高版本中的 Uint8Array 的实例。对于较低的 Node.js 版本,您必须实现 toArrayBuffer 函数。【参考方案13】:

NodeJS,在某一时刻(我认为它是 v0.6.x)支持 ArrayBuffer。我创建了一个用于 base64 编码和解码 here 的小型库,但是自从更新到 v0.7 后,测试(在 NodeJS 上)失败了。我正在考虑创建一些使其正常化的东西,但在此之前,我认为应该使用 Node 的原生 Buffer

【讨论】:

以上是关于将二进制 NodeJS 缓冲区转换为 JavaScript ArrayBuffer的主要内容,如果未能解决你的问题,请参考以下文章

nodejs-09-Buffer

nodejs-09-Buffer

nodejs从缓冲区数据转换为字节数组

NodeJS 将二进制缓冲区写入文件

如何读取缓冲区的前 n 个字节并在 NodeJS 中转换为字符串?

MongoDB & Multer - 如何将缓冲区二进制文件转换为图像?