【问题标题】:Convert a binary NodeJS Buffer to JavaScript ArrayBuffer将二进制 NodeJS 缓冲区转换为 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。

标签: javascript node.js binary buffer arraybuffer


【解决方案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://stackoverflow.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)。

    【讨论】:

    【解决方案4】:

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

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

    还有更多。

    【讨论】:

      【解决方案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 propertylength property,它们继承自Uint8Arraythe 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)
        

        【讨论】:

        【解决方案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
        • 在这种情况下,它与stackoverflow.com/a/19544002/1218408 相同,并且仍然不需要在stackoverflow.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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-02
          • 1970-01-01
          • 2014-02-21
          • 2016-09-16
          • 2012-10-03
          • 2019-06-04
          • 2012-02-11
          相关资源
          最近更新 更多