如何将NodeJS二进制缓冲转换为JavaScript ArrayBuffer?
如何将NodeJS二进制缓冲转换为JavaScript ArrayBuffer?
在Node.js 4.x及以上版本中,Buffer
实例同时也是Uint8Array
实例。因此,最高效的解决方案是直接访问缓冲区自身的.buffer
属性,如https://dev59.com/w2oy5IYBdhLWcg3wWss_#31394257所述。如果需要向另一个方向转换,Buffer
构造函数还可以使用ArrayBufferView
参数。
请注意,这不会创建副本,这意味着对任何ArrayBufferView
的写入都将写入到原始的Buffer
实例中。
ArrayBuffer
作为v8的一部分,但是Buffer
类提供了更灵活的API。要读取或写入ArrayBuffer
,您只需要创建视图并复制即可。
从Buffer
到ArrayBuffer
:
function toArrayBuffer(buffer) {
const arrayBuffer = new ArrayBuffer(buffer.length);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < buffer.length; ++i) {
view[i] = buffer[i];
}
return arrayBuffer;
}
从ArrayBuffer到Buffer:
function toBuffer(arrayBuffer) {
const buffer = Buffer.alloc(arrayBuffer.byteLength);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < buffer.length; ++i) {
buffer[i] = view[i];
}
return buffer;
}
size&0xfffffffe
之前,复制32位整数,然后,如果还剩1个字节,请复制8位整数,如果还剩2个字节,请复制16位整数,如果还剩3个字节,请复制16位和8位整数。 - Triang3lab
而不是 view
? - jcalfee314ab
?ab
没有被使用过吗?我一直得到的结果是 {}
。 - Andi Gigaslice()
方法返回一个新的 ArrayBuffer
,其内容是从起始位置(含)到结束位置(不含)的字节副本。 - Константин Ван"从ArrayBuffer到Buffer"可以通过以下方式完成:
var buffer = Buffer.from( new Uint8Array(arrayBuffer) );
Buffer
是 Uint8Array
,因此您只需要切片(复制)支持的 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
(默认情况下小于4 kB,等于池大小的一半)可以是共享ArrayBuffer
的视图。如果不进行切片,可能会得到包含另一个Buffer
数据的ArrayBuffer
。请参见文档中的解释。
如果您最终需要TypedArray
,可以创建一个而无需复制数据:
// Create a new view of the ArrayBuffer without copying
let ui32 = new Uint32Array(b.buffer, b.byteOffset, b.byteLength / Uint32Array.BYTES_PER_ELEMENT);
使用Martin Thomson的答案,其运行时间为O(n)。 (请参见我对他的回答中关于非优化的评论。使用DataView会很慢。即使您需要翻转字节,也有更快的方法。)
您可以使用https://www.npmjs.com/package/memcpy在两个方向(Buffer到ArrayBuffer和反向)之间进行转换。它比此处发布的其他答案更快,并且是一个编写良好的库。 Node 0.12到iojs 3.x需要ngossen的分支(请参见此处)。
.byteLength
”和“.byteOffset
”的文档在哪里可以找到? - Константин Ванvar ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
保存了我的一天 - Alexey Sh.slice()
不是 O(1) 而是 O(n),因为它会创建一个副本:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice - Mike Frysinger更快的写法
var arrayBuffer = new Uint8Array(nodeBuffer).buffer;
然而,对于包含1024个元素的缓冲区,这似乎比建议使用的toArrayBuffer函数运行得慢大约4倍。
Buffer
实例也是Uint8Array
实例的情况。对于较低版本的Node.js,您必须实现一个toArrayBuffer
函数。 - Benny CodeBuffer
只是查看ArrayBuffer
的一种方式。实际上,一个Buffer
是一个FastBuffer
,它扩展
(继承自)Uint8Array
,这是实际内存的八位元组视图(“部分访问器”),即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;
ArrayBuffer
的大小和其视图的大小可能会有所不同。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.
FastBuffer
的内存分配。FastBuffer
根据大小采用两种不同的内存分配方式。
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));
}
内存池是一个固定大小的预分配内存块,用于保存小型内存块以供Buffer
使用。使用它可以将小型内存块紧密地放在一起,因此防止了由于对小型内存块进行单独管理(分配和释放)而导致的碎片化。
在这种情况下,内存池是默认大小为8 KiB的ArrayBuffer
,这在Buffer.poolSize
中指定。当需要为Buffer
提供小型内存块时,它会检查最后一个内存池是否有足够的可用内存来处理此操作;如果有,它将创建一个Buffer
,该Buffer
“查看”内存池中给定部分的内存块,否则,它将创建一个新的内存池,以此类推。
Buffer
的底层ArrayBuffer
。buffer
属性 (即从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.
ArrayBuffer
的大小是固定的,因此我们需要通过复制部分来提取它。为此,我们使用 Buffer
的 byteOffset
属性 和 length
属性,这些属性都是从 Uint8Array
继承而来的,以及 ArrayBuffer.prototype.slice
方法,该方法可以复制 ArrayBuffer
的一部分。本文中的 slice()
方法受到了@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
如果您只需将结果用作只读,或者可以修改输入缓冲区
的内容,则可以避免不必要的内存复制。
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
obtain_arraybuffer
函数中:buf.buffer.subarray
似乎不存在。你是不是想用 buf.buffer.slice
? - Marcin CzenkoArrayBuffer.prototype.slice
,后来将其修改为 Uint8Array.prototype.subarray
。哦,我做错了。可能当时有点困惑。现在多亏了您,一切都很好。 - Константин Ван使用以下优秀的npm包:to-arraybuffer
。
或者,您可以自己实现。如果您的缓冲区称为buf
,请执行以下操作:
buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)
Buffer
是 ArrayBuffer
的一种 view
。您可以使用 buffer
属性访问内部封装的 ArrayBuffer
。
这是共享内存,无需复制。
const arrayBuffer = theBuffer.buffer
如果您想要数据的副本,请从原始的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
ArrayBuffer
视为一种类型化的Buffer
。ArrayBuffer
总是需要一个类型(被称为“Array Buffer View”)。通常,Array Buffer View的类型为Uint8Array
或Uint16Array
。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!"
buffer
https://github.com/feross/buffer,它试图提供一个与node的Buffer API完全相同的API,并允许:
还有其他一些功能。这个代理将缓冲区公开为任何TypedArrays之一,而不需要进行任何复制。
https://www.npmjs.com/package/node-buffer-as-typedarray
它只适用于LE,但可以轻松移植到BE。 此外,我从未真正测试过它的效率。
Float32Array
,其中它占用4个字节。如果您想快速将这些浮点数序列化到文件中,则需要使用Buffer
,因为将其序列化为JSON需要很长时间。 - nponeccopconst file = fs.readFileSync(filePath);
,那么我该怎么使用呢?... 30分钟后,哇,我想念C语言。 - William Entriken