将二进制 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】:
Buffer
是 view
的 ArrayBuffer
。您可以使用buffer
属性访问内部包装的ArrayBuffer
。
这是共享内存,不需要复制。
const arrayBuffer = theBuffer.buffer
如果您想要数据的copy
,请从原始Buffer
(不是来自包装的ArrayBuffer)创建另一个Buffer
,然后引用其包装的ArrayBuffer
。
const newArrayBuffer = Buffer.from(theBuffer).buffer
作为参考,从另一个方向,从 ArrayBuffer
到 Buffer
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&0xfffffffe
,复制 32 位整数,然后,如果剩余 1 个字节,则复制 8 位整数,如果是 2 个字节,则复制 16 位整数,如果是 3 个字节,则复制 16 位和 8 位整数。
查看 kraag22 的答案,了解其中一半的更简单实现。
已经用一个供浏览器使用的模块测试了 Buffer -> ArrayBuffer,它运行良好。谢谢!
为什么会返回ab
? ab
什么都没做?结果我总是得到
。
'slice()
方法返回一个新的ArrayBuffer
,其内容是此ArrayBuffer
从开始、包含、到结束、不包含的字节的副本。' - MDN ArrayBuffer.prototype.slice()
【参考方案3】:
无依赖,最快,Node.js 4.x 及更高版本
Buffer
s 是Uint8Array
s,所以你只需要将其支持的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
s(默认小于 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) 时间内运行。 (另请参阅我对 cmets 关于非优化的回答。使用 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 包:buffer
https://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.0class 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.0function 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.0function createUnsafeBuffer(size)
return new FastBuffer(createUnsafeArrayBuffer(size));
“内存池”是什么意思?
memory pool 是一个固定大小的预分配内存块,用于为Buffer
s 保留小尺寸内存块。使用它可以将小内存块紧密地结合在一起,从而防止fragmentation 由小内存块的单独管理(分配和释放)引起。
在这种情况下,内存池为ArrayBuffer
s,默认大小为8 KiB,在Buffer.poolSize
中指定。当它为Buffer
提供一个小内存块时,它会检查最后一个内存池是否有足够的可用内存来处理这个问题;如果是,它会创建一个Buffer
,“查看”内存池的给定部分块,否则,它会创建一个新的内存池,依此类推。
您可以访问Buffer
的底层ArrayBuffer
。 Buffer
的buffer
property(即继承自Uint8Array
)持有它。 一个“小”Buffer
的buffer
属性是一个代表整个内存池的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
的大小是固定的,因此我们需要通过复制该部分来将其提取出来。为此,我们使用Buffer
的byteOffset
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。性能提升
如果要将结果作为只读使用,或者修改输入Buffer
s'的内容也可以,可以避免不必要的内存复制。
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_arraybuffer
:buf.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 的类型为 Uint8Array
或 Uint16Array
。
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的主要内容,如果未能解决你的问题,请参考以下文章