Chuyển đổi bộ đệm NodeJS nhị phân sang JavaScript ArrayBuffer


133

Làm cách nào tôi có thể chuyển đổi bộ đệm nhị phân NodeJS thành JavaScript ArrayBuffer?


1
Tôi tò mò về lý do tại sao bạn cần phải làm điều này?
Chris Biscardi

14
một ví dụ điển hình sẽ là viết một thư viện hoạt động với File's trong trình duyệt và cả cho các tệp NodeJS?
fbstj

1
hoặc sử dụng thư viện trình duyệt trong NodeJS
OrangeDog

1
Một lý do khác là một float chiếm quá nhiều byte RAM khi được lưu trữ trong một Array. Vì vậy, để lưu trữ nhiều float, bạn cần Float32Array4 byte. Và nếu bạn muốn tuần tự hóa nhanh chóng các số float đó vào một tệp mà bạn cần Buffer, vì việc tuần tự hóa thành JSON mất nhiều thời gian.
nponeccop

Tôi muốn biết chính xác điều tương tự để gửi dữ liệu chung bằng WebRTC và không thể tin được rằng có rất nhiều câu trả lời ở đây có rất nhiều lượt thích, nhưng đừng trả lời câu hỏi thực tế ...
Felix Crazzolara

Câu trả lời:


134

Các trường hợp Buffercũng là các phiên bản củaUint8Array node.js 4.x trở lên. Do đó, giải pháp hiệu quả nhất là truy cập buf.buffertrực tiếp vào tài sản, theo https://stackoverflow.com/a/31394257/1375574 . Trình xây dựng bộ đệm cũng nhận một đối số ArrayBufferView nếu bạn cần đi theo hướng khác.

Lưu ý rằng điều này sẽ không tạo ra một bản sao, điều đó có nghĩa là việc ghi vào bất kỳ ArrayBufferView nào sẽ ghi vào đối tượng Bộ đệm ban đầu.


Trong các phiên bản cũ hơn, node.js có cả ArrayBuffer là một phần của v8, nhưng lớp Buffer cung cấp API linh hoạt hơn. Để đọc hoặc ghi vào ArrayBuffer, bạn chỉ cần tạo chế độ xem và sao chép.

Từ bộ đệm đến ArrayBuffer:

function toArrayBuffer(buf) {
    var ab = new ArrayBuffer(buf.length);
    var view = new Uint8Array(ab);
    for (var i = 0; i < buf.length; ++i) {
        view[i] = buf[i];
    }
    return ab;
}

Từ ArrayBuffer đến Bộ đệm:

function toBuffer(ab) {
    var buf = Buffer.alloc(ab.byteLength);
    var view = new Uint8Array(ab);
    for (var i = 0; i < buf.length; ++i) {
        buf[i] = view[i];
    }
    return buf;
}

5
Tôi cũng khuyên bạn nên tối ưu hóa điều này bằng cách sao chép số nguyên khi có thể bằng DataView. Cho đến khi size&0xfffffffe, sao chép số nguyên 32 bit, sau đó, nếu còn 1 byte, sao chép số nguyên 8 bit, nếu 2 byte, sao chép số nguyên 16 bit và nếu 3 byte, sao chép số nguyên 16 bit và 8 bit.
Triang3l

3
Xem câu trả lời của kraag22 để thực hiện đơn giản hơn một nửa số này.
OrangeDog

Đã kiểm tra Bộ đệm -> ArrayBuffer với một mô-đun dành cho sử dụng trình duyệt và nó đang hoạt động rất tốt. Cảm ơn!
pospi

3
Tại sao được abtrả lại? Không có gì được thực hiện với ab? Tôi luôn luôn nhận được {}kết quả.
Andi Giga

1
' slice()Phương thức trả về một cái mới ArrayBuffercó nội dung là một bản sao của ArrayBuffercác byte này từ đầu, bao gồm, đến cuối, độc quyền.' - MDNArrayBuffer.prototype.slice()
Toàn cảnh

61

Không phụ thuộc, nhanh nhất, Node.js 4.x trở lên

Buffers là Uint8Arrays, vì vậy bạn chỉ cần cắt (sao chép) vùng của nó 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);

Các slicecông cụ bù và được yêu cầu vì các Buffers nhỏ (dưới 4 kB theo mặc định, một nửa kích thước nhóm ) có thể là các lượt xem trên một chia sẻ ArrayBuffer. Nếu không cắt, bạn có thể kết thúc với ArrayBufferdữ liệu chứa từ dữ liệu khác Buffer. Xem giải thích trong các tài liệu .

Nếu cuối cùng bạn cần một TypedArray, bạn có thể tạo một cái mà không cần sao chép dữ liệu:

// Create a new view of the ArrayBuffer without copying
let ui32 = new Uint32Array(b.buffer, b.byteOffset, b.byteLength / Uint32Array.BYTES_PER_ELEMENT);

Không phụ thuộc, tốc độ vừa phải, bất kỳ phiên bản nào của Node.js

Sử dụng câu trả lời của Martin Thomson , chạy trong O (n) thời gian . (Xem thêm câu trả lời của tôi để nhận xét về câu trả lời của anh ấy về việc không tối ưu hóa. Sử dụng DataView rất chậm. Ngay cả khi bạn cần lật byte, có nhiều cách nhanh hơn để làm như vậy.)

Phụ thuộc, nhanh, Node.js ≤ 0,12 hoặc iojs 3.x

Bạn có thể sử dụng https://www.npmjs.com/package/memcpy để đi theo một trong hai hướng (Bộ đệm cho ArrayBuffer và quay lại). Nó nhanh hơn các câu trả lời khác được đăng ở đây và là một thư viện được viết tốt. Nút 0,12 đến iojs 3.x yêu cầu ngã ba của ngossen (xem phần này ).


Nó không biên dịch lại nút> 0.12
Pawel Veselov

1
Sử dụng ngã ba của ngossen: github.com/dcodeIO/node-memcpy/pull/6 . Xem thêm câu trả lời mới của tôi nếu bạn đang sử dụng nút 4+.
ZachB

Trường hợp .byteLength.byteOffsettài liệu ở đâu?
Toàn cảnh

1
@ K._ Những thuộc tính này được thừa hưởng từ TypedArray: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
ZachB

1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);đã cứu ngày của tôi
Alexey Sh.

55

"Từ ArrayBuffer đến Buffer" có thể được thực hiện theo cách này:

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

27
Điều đó trái ngược với những gì OP muốn.
Alexander Gonchiy

42
Nhưng đó là những gì tôi muốn giải quyết vấn đề của mình và vui mừng vì tôi đã tìm ra giải pháp.
Maciej Krawchot

27

Một cách nhanh hơn để viết nó

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

Tuy nhiên, điều này dường như chạy chậm hơn khoảng 4 lần so với hàm toArrayBuffer được đề xuất trên bộ đệm có 1024 phần tử.


3
Ngoài ra: @trevnorris nói "bắt đầu từ [V8] 4.3 Bộ đệm được hỗ trợ bởi Uint8Array", vì vậy có thể điều này nhanh hơn bây giờ ...
ChrisV

Xem câu trả lời của tôi cho cách an toàn để làm điều này.
ZachB

3
Đã thử nghiệm nó với v5.6.0 và nó là nhanh nhất
daksh_019

1
Điều này chỉ hoạt động vì các phiên bản Buffercũng là các phiên bản Uint8Arraytrong Node.js 4.x trở lên. Đối với các phiên bản Node.js thấp hơn, bạn phải triển khai một toArrayBufferhàm.
Benny Neugebauer

14

1. A Bufferchỉ là một cái nhìn để nhìn vào một ArrayBuffer.

A Buffer, trên thực tế, là một FastBuffer, extends(kế thừa từ) Uint8Array, là một khung nhìn đơn vị octet (một phần truy cập một phần của bộ nhớ) của bộ nhớ thực, một ArrayBuffer.

  📜 Node.js 9.4.0/lib/buffer.js#L65-L73
class FastBuffer extends Uint8Array {
  constructor(arg1, arg2, arg3) {
    super(arg1, arg2, arg3);
  }
}
FastBuffer.prototype.constructor = Buffer;
internalBuffer.FastBuffer = FastBuffer;

Buffer.prototype = FastBuffer.prototype;

2. Kích thước của một ArrayBuffervà kích thước của khung nhìn của nó có thể thay đổi.

Lý do số 1 : Buffer.from(arrayBuffer[, byteOffset[, length]]).

Với Buffer.from(arrayBuffer[, byteOffset[, length]]), bạn có thể tạo một Bufferchỉ định bên dưới ArrayBuffervà vị trí và kích thước của khung nhìn.

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.

Lý do # 2: FastBuffercấp phát bộ nhớ.

Nó phân bổ bộ nhớ theo hai cách khác nhau tùy thuộc vào kích thước.

  • Nếu kích thước nhỏ hơn một nửa kích thước của nhóm bộ nhớ và không bằng 0 (có nghĩa là nhỏ) : nó sử dụng nhóm bộ nhớ để chuẩn bị bộ nhớ cần thiết.
  • Khác : nó tạo ra một chuyên dụng ArrayBufferphù hợp chính xác với bộ nhớ cần thiết.
  📜 Node.js 9.4.0/lib/buffer.js#L306-L320
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);
  }
}
  📜 Node.js 9.4.0/lib/buffer.js#L98-L100
function createUnsafeBuffer(size) {
  return new FastBuffer(createUnsafeArrayBuffer(size));
}

Bạn có ý nghĩa gì với một nhóm bộ nhớ của người Viking ?

Nhóm bộ nhớ là khối bộ nhớ được phân bổ trước có kích thước cố định để giữ các khối bộ nhớ kích thước nhỏ cho Buffers. Sử dụng nó giữ các khối bộ nhớ kích thước nhỏ lại với nhau chặt chẽ, do đó ngăn ngừa sự phân mảnh gây ra bởi quản lý riêng biệt (phân bổ và phân bổ) các khối bộ nhớ kích thước nhỏ.

Trong trường hợp này, các nhóm bộ nhớ là ArrayBuffers có kích thước là 8 KiB theo mặc định, được chỉ định trong Buffer.poolSize. Khi cần cung cấp một đoạn bộ nhớ kích thước nhỏ cho a Buffer, nó sẽ kiểm tra xem nhóm bộ nhớ cuối có đủ bộ nhớ khả dụng để xử lý việc này hay không; nếu vậy, nó tạo ra một lượt xemBufferViking xem một phần nhất định của nhóm bộ nhớ, nếu không, nó tạo ra một nhóm bộ nhớ mới, v.v.


Bạn có thể truy cập vào cơ sở ArrayBuffercủa a Buffer. Các Buffer's buffertài sản (có nghĩa là, thừa hưởng từ Uint8Array) nắm giữ nó. Một tài sản nhỏ Buffer của thành phố bufferlà một ArrayBufferđại diện cho toàn bộ bộ nhớ. Vì vậy, trong trường hợp này, ArrayBufferBufferkích thước khác nhau.

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. Vì vậy, chúng tôi cần giải nén bộ nhớ mà nó xem .

An ArrayBufferđược cố định về kích thước, vì vậy chúng ta cần trích xuất nó bằng cách tạo một bản sao của bộ phận. Để làm điều này, chúng tôi sử dụng Buffer's byteOffsettài sảnlengthbất động sản , được thừa hưởng từ Uint8Array, và các ArrayBuffer.prototype.slicephương pháp , mà làm cho một bản sao của một phần của một ArrayBuffer. Các slice()phương pháp -ing ở đây được lấy cảm hứng từ @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. Cải thiện hiệu suất

Nếu bạn sử dụng kết quả dưới dạng chỉ đọc hoặc sửa đổi Buffernội dung của đầu vào , bạn có thể tránh sao chép bộ nhớ không cần thiết.

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

4
Đây là tất cả tốt và tốt ... nhưng bạn đã thực sự trả lời câu hỏi của OP? Nếu bạn đã làm, nó sẽ bị chôn vùi ...
Tustin2121

Câu trả lời chính xác! Trong obtain_arraybuffer: buf.buffer.subarraydường như không tồn tại. Ý bạn là buf.buffer.sliceở đây?
năng suất hàng ngày

@everydayproductive Cảm ơn bạn. Như bạn có thể thấy trong lịch sử chỉnh sửa, tôi thực sự đã sử dụng ArrayBuffer.prototype.slicevà sau đó sửa đổi nó thành Uint8Array.prototype.subarray. Ồ, và tôi đã làm sai. Có lẽ đã có một chút bối rối trở lại sau đó. Bây giờ tất cả đều tốt nhờ có bạn.
Toàn cảnh

12

Sử dụng gói npm tuyệt vời sau : to-arraybuffer.

Hoặc, bạn có thể tự thực hiện nó. Nếu bộ đệm của bạn được gọi buf, hãy làm điều này:

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


1

Bạn có thể nghĩ về một ArrayBuffernhư là một gõ Buffer.

Một ArrayBufferdo đó luôn cần một kiểu (cái gọi là "mảng đệm View"). Thông thường, Array Buffer View có một loại Uint8Arrayhoặc Uint16Array.

Có một bài viết hay từ Renato Mangini về việc chuyển đổi giữa ArrayBuffer và String .

Tôi đã tóm tắt các phần thiết yếu trong một ví dụ mã (cho Node.js). Nó cũng cho thấy làm thế nào để chuyển đổi giữa đánh máy ArrayBuffervà không được gõ 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!"

0

Tôi đã thử ở trên cho Float64Array và nó không hoạt động.

Cuối cùng tôi đã nhận ra rằng thực sự dữ liệu cần phải được đọc 'VÀO' chế độ xem trong các đoạn chính xác. Điều này có nghĩa là đọc 8 byte mỗi lần từ Bộ đệm nguồn.

Dù sao đây là những gì tôi đã kết thúc với ...

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++;
}

Đó là lý do tại sao câu trả lời của Martin Thomson sử dụng Uint8Array - không rõ ràng về kích thước của các yếu tố. Các Buffer.read*phương pháp đều chậm, cũng có.
ZachB

Nhiều khung nhìn mảng được gõ có thể tham chiếu cùng một ArrayBuffer bằng cùng một bộ nhớ. Mỗi giá trị trong Bộ đệm là một byte, vì vậy bạn cần đặt nó vào một mảng có kích thước phần tử là 1 byte. Bạn có thể sử dụng phương thức của Martin, sau đó tạo một Float64Array mới bằng cách sử dụng cùng một bộ tạo mảng trong hàm tạo.
ZachB

0

Proxy này sẽ hiển thị bộ đệm như bất kỳ TypedArrays nào, mà không có bất kỳ bản sao nào. :

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

Nó chỉ hoạt động trên LE, nhưng có thể dễ dàng chuyển sang BE. Ngoài ra, không bao giờ phải thực sự kiểm tra hiệu quả của việc này.


1
Mặc dù liên kết này có thể trả lời câu hỏi, tốt hơn là bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo. Câu trả lời chỉ liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi
koceeng

2
Từ ngữ của tôi có vẻ không chính thức lắm, nhưng nó cung cấp đủ thông tin để tạo lại giải pháp. Giải pháp dựa trên Đối tượng Proxy JavaScript để bọc Bộ đệm NodeJS riêng với các getters và setters được sử dụng bởi TypedArrays. Điều này làm cho cá thể Bộ đệm tương thích với bất kỳ thư viện nào yêu cầu giao diện Typed Array. Đây là câu trả lời mà người đăng ban đầu hy vọng, nhưng hãy loại bỏ nó vì nó không phù hợp với biệt ngữ học thuật / công ty của bạn. Xem nếu tôi quan tâm.
Dlabz


-1

NodeJS, tại một thời điểm (tôi nghĩ đó là v0.6.x) có hỗ trợ ArrayBuffer. Tôi đã tạo một thư viện nhỏ để mã hóa và giải mã base64 tại đây , nhưng kể từ khi cập nhật lên v0.7, các thử nghiệm (trên NodeJS) đều thất bại. Tôi đang nghĩ đến việc tạo ra thứ gì đó bình thường hóa điều này, nhưng cho đến lúc đó, tôi cho rằng Buffernên sử dụng nguồn gốc của Node .


-6

Tôi đã cập nhật nút của mình lên Phiên bản 5.0.0 và tôi làm việc với điều này:

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
}

Tôi sử dụng nó để kiểm tra hình ảnh đĩa vhd của tôi.


Đây trông giống như một phương thức dựa trên tuần tự hóa chuyên biệt (và chậm), không phải là một phương thức chung để chuyển đổi sang / từ Buffer / ArrayBuffer?
ZachB

@ZachB nó là phương thức chung cho V5.0.0 + [only] = =.
Miguel Valentine ngày

toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- đây là trả về một chuỗi các chuỗi, không phải số nguyên / byte.
ZachB

Mảng trả về @ZachB -> danh sách trả về. tôi sửa int-> chuỗi cho thiết bị xuất chuẩn
Miguel Valentine

Trong trường hợp đó, nó giống như stackoverflow.com/a/19544002/1218408 và vẫn không cần kiểm tra bù byte trong stackoverflow.com/a/31394257/1218408 .
ZachB
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.