将一个二进制的NodeJS Buffer转换为JavaScript的ArrayBuffer

如何将一个NodeJS二进制缓冲区转换为JavaScript数组缓冲区?

245249 次浏览

NodeJS,在某一时刻(我想是v0.6.x)有ArrayBuffer支持。我为base64编码和解码在这里创建了一个小库,但自从更新到v0.7后,测试(在NodeJS上)失败了。我正在考虑创建一些规范化的东西,但在此之前,我认为应该使用Node的本机Buffer

__ABC0的实例也是Uint8Array的实例在node.js 4。X和更高。因此,最有效的解决方案是直接访问buf.buffer属性,就像https://stackoverflow.com/a/31394257/1375574一样。Buffer构造函数也接受一个ArrayBufferView参数,如果你需要走另一个方向。

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


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

从Buffer到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;
}

"From ArrayBuffer to Buffer"可以这样做:

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

一种更快的写法

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

然而,在包含1024个元素的缓冲区上,这似乎比建议的toArrayBuffer函数慢了大约4倍。

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

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

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

我尝试了上面的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++;
}

没有依赖,最快,Node.js 4。X及以后

__abc0是__abc1,所以你只需要切片(复制)它的备份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和偏移量是要求,因为小的__abc1(默认小于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);

没有依赖,速度适中,适用于任何版本的Node.js

使用马丁·汤姆森的回答,它在O (n)时间内运行。(参见我对他关于非优化的回答的回复。使用DataView很慢。即使你需要转换字节,也有更快的方法。)

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

你可以使用https://www.npmjs.com/package/memcpy在任何一个方向(缓冲区到ArrayBuffer和返回)。它比这里发布的其他答案要快,而且是一个写得很好的库。节点0.12到iojs 3。x require ngossen's fork(参见)。

我已经把我的节点更新到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磁盘映像。

这个代理将把缓冲区暴露为任何typedarray,没有任何副本。:

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

它只能在LE上工作,但可以很容易地移植到be。 而且,从来没有真正测试过这有多高效

你可以把ArrayBuffer看作是类型化的Buffer

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

Renato Mangini在在ArrayBuffer和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!"

1. Buffer只是一个用于查找ArrayBuffer视图

Buffer实际上是一个FastBuffer,而extends(继承自)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;

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 (" small "):它使用内存池来准备所需的内存。
  • 其他的:它创建了一个专门的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));
}

内存池”是什么意思?

< / em > < em >内存池是一个固定大小的预分配的内存块,用于为__abc0保存小型内存块。使用它将小内存块紧密地放在一起,从而防止了小内存块的单独管理(分配和释放)所导致的< em > < / em >分裂

在本例中,内存池为__abc0,其大小默认为8 KiB,在Buffer.poolSize中指定。当它要为Buffer提供一个小的内存块时,它会检查最后一个内存池是否有足够的可用内存来处理这个;如果是,它创建一个Buffer“视图”内存池的给定部分块,否则,它创建一个新的内存池,以此类推。


你可以访问Buffer的底层ArrayBufferBufferBuffer0(即继承自Uint8Array)保存它。所以在这种情况下,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.因此,我们需要提取内存为的观点

ArrayBuffer的大小是固定的,所以我们需要通过复制该部分来提取它。为此,我们使用BufferbyteOffset财产length财产,它们继承自Uint8Array,还有Buffer0,它们复制了ArrayBuffer的一部分。这里的slice()-ing方法是受到Buffer1的启发。

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

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

它试图提供一个API,是100%相同的节点的缓冲区API,并允许:

还有更多。

BufferArrayBufferview。你可以使用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