İkili NodeJS Buffer'ı JavaScript ArrayBuffer'a dönüştürme


133

NodeJS ikili arabelleğini JavaScript ArrayBuffer'a nasıl dönüştürebilirim?


1
Bunu neden yapmanız gerektiğini merak ediyorum?
Chris Biscardi

14
Tarayıcılarda File's ile ve ayrıca NodeJS dosyaları için çalışan bir kitaplık yazmak iyi bir örnek olabilir mi?
fbstj

1
veya NodeJS'de bir tarayıcı kitaplığı kullanarak
OrangeDog

1
Diğer bir neden de, bir float dosyasının bir .mc dosyasında saklandığında çok fazla bayt RAM almasıdır Array. Bu nedenle, birçok kayan noktayı depolamak Float32Arrayiçin 4 bayt alan yere ihtiyacınız vardır . Ve bu kayan değerlerin bir dosyaya hızlı bir şekilde serileştirilmesini istiyorsanız Buffer, JSON'a serileştirme zaman aldığından, a'ya ihtiyacınız vardır .
nponeccop

WebRTC kullanarak jenerik verileri göndermek için tam olarak aynı şeyi bilmek istiyorum ve buradaki pek çok cevabın bu kadar çok beğenilmesi inanılmaz ama asıl soruyu cevaplamayın ...
Felix Crazzolara,

Yanıtlar:


134

Örnekleri BufferayrıcaUint8Array node.js 4.x ve sonraki sürümlerindeki örneklerdir . Bu nedenle, en verimli çözüm, https://stackoverflow.com/a/31394257/1375574buf.buffer uyarınca mülke doğrudan erişim sağlamaktır . Buffer yapıcısı, diğer yöne gitmeniz gerekiyorsa ayrıca bir ArrayBufferView bağımsız değişkeni alır.

Bunun bir kopya oluşturmayacağını, yani herhangi bir ArrayBufferView'e yazmanın orijinal Buffer örneğine yazacağı anlamına geldiğini unutmayın.


Eski sürümlerde, node.js, v8'in bir parçası olarak hem ArrayBuffer'a sahiptir, ancak Buffer sınıfı daha esnek bir API sağlar. Bir ArrayBuffer'ı okumak veya yazmak için, yalnızca bir görünüm oluşturmanız ve kopyalamanız gerekir.

Arabellekten ArrayBuffer'a:

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

ArrayBuffer'dan Buffer'a:

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
DataView kullanarak mümkün olduğunda tam sayıları kopyalayarak bunu optimize etmenizi de tavsiye ederim. Ta ki size&0xfffffffe32 bitlik tam sayıları kopyalayın, sonra 1 bayt kaldı ise 8 bitlik tamsayıyı, 2 bayt ise 16 bitlik tamsayıyı kopyalayın ve 3 bayt ise 16 bit ve 8 bit tamsayıyı kopyalayın.
Triang3l

3
Bunun yarısının daha basit bir uygulaması için kraag22'nin cevabına bakın.
OrangeDog

Buffer -> ArrayBuffer'ı tarayıcı kullanımı için tasarlanmış bir modülle test ettiniz ve mükemmel çalışıyor. Teşekkürler!
pospi

3
Neden abiade edilir? Hiçbir şey yapılmadı abmı? Her zaman {}sonuç olarak alırım .
Andi Giga

1
' slice() Yöntem ArrayBuffer, içeriği ArrayBufferbaştan, dahil, sona kadar, hariç olmak üzere bu baytların bir kopyası olan yeni bir döndürür .' - MDNArrayBuffer.prototype.slice()
Константин Ван

61

Bağımlılık yok, en hızlı, Node.js 4.x ve sonrası

Buffers 'dir Uint8Array, bu yüzden sadece destek bölgesini dilimlemeniz (kopyalamanız) gerekir 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);

sliceVe ofset şeyler olduğu gerekli küçük çünkü Bufferler (varsayılan olarak 4'ten az kB, yarım havuz boyutu ) paylaşılan üzerinde görüşlerini olabilir ArrayBuffer. Dilimleme yapmadan, bir ArrayBufferbaşkasının verilerini içeren bir sonuç elde edebilirsiniz Buffer. Dokümanlardaki açıklamaya bakın .

Nihayetinde a'ya ihtiyacınız varsa TypedArray, verileri kopyalamadan bir tane oluşturabilirsiniz:

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

Bağımlılık yok, orta hız, Node.js'nin herhangi bir sürümü

O (n) zamanda çalışan Martin Thomson'ın cevabını kullanın . (Optimizasyon olmayanlarla ilgili yanıtı hakkındaki yorumlara da bakın. DataView kullanmak yavaştır. Baytları çevirmeniz gerekse bile bunu yapmanın daha hızlı yolları vardır.)

Bağımlılık, hızlı, Node.js ≤ 0.12 veya iojs 3.x

Her iki yönde de gitmek için https://www.npmjs.com/package/memcpy kullanabilirsiniz (Arabellekten ArrayBuffer'a ve geri). Burada yayınlanan diğer cevaplardan daha hızlı ve iyi yazılmış bir kitaplık. (Bkz adlı çatal ngossen gerektiren 3.x iojs aracılığıyla Düğüm 0.12 bu ).


> 0.12 düğümünü tekrar
derlemez

1
Ngossen'in çatalını kullanın: github.com/dcodeIO/node-memcpy/pull/6 . Ayrıca 4+ düğüm kullanıyorsanız yeni cevabıma da bakın.
ZachB

Nerede .byteLengthve .byteOffsetbelgelendi?
Константин Ван


1
var ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);günümü kurtardı
Alexey Sh.

57

"ArrayBuffer'dan Buffer'a" şu şekilde yapılabilir:

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

27
Bu, OP'nin istediği şeyin tam tersi.
Alexander Gonchiy

43
Ama sorunumu araştırmak istediğim şey buydu ve çözümü bulduğuma sevindim.
Maciej Krawczyk

27

Yazmanın daha hızlı bir yolu

var arrayBuffer = new Uint8Array(nodeBuffer).buffer;

Ancak bu, 1024 öğeli bir arabellekte önerilen toArrayBuffer işlevinden yaklaşık 4 kat daha yavaş çalışıyor gibi görünüyor.


3
Geç ekleme: @trevnorris diyor bu yüzden muhtemelen bu ... artık daha hızlı, "4,3 Tamponlar Uint8Array ile desteklenmektedir [V8] ayından itibaren"
ChrisV

Bunu yapmanın güvenli yolu için cevabıma bakın.
ZachB

3
V5.6.0 ile test edildi ve en hızlısı oldu
daksh_019

1
Bu yalnızca, Bufferörnekleri aynı zamanda Uint8ArrayNode.js 4.x ve üzeri örnekler olduğu için işe yarar. Daha düşük Node.js sürümleri için bir toArrayBufferişlev uygulamanız gerekir .
Benny Neugebauer

14

1. A Buffer, bir içine bakmak için bir görünümdürArrayBuffer .

BufferAslında A , gerçek belleğin sekizli birim görünümü ("kısmi erişimci") FastBufferolan extends(miras alan) Uint8Arraya'dır , an .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. An'ın boyutu ArrayBufferve görünümünün boyutu değişebilir.

Sebep # 1: Buffer.from(arrayBuffer[, byteOffset[, length]]).

İle Buffer.from(arrayBuffer[, byteOffset[, length]]), Buffertemelini ArrayBufferve görünümün konumunu ve boyutunu belirterek bir oluşturabilirsiniz .

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.

Neden # 2: FastBufferbellek ayırma.

Belleği boyuta bağlı olarak iki farklı şekilde tahsis eder.

  • Boyut, bir bellek havuzunun boyutunun yarısından küçükse ve 0 değilse ("küçük") : gerekli belleği hazırlamak için bir bellek havuzunu kullanır.
  • Aksi takdirde: ArrayBuffergerekli belleğe tam olarak uyan bir adanmış oluşturur .
  📜 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));
}

" Hafıza havuzu " ile neyi kastediyorsunuz ?

Bir hafıza havuzu sabit boyutlu bir ön tahsis küçük boyutlu bellek parçaları tutmak için bellek bloğu Buffers. Kullanılması, küçük boyutlu bellek yığınlarını sıkıca bir arada tutar, böylece parçalanmayı önler ayrı yönetiminden (ayırma ve ayırma) kaynaklanan .

Bu durumda, bellek havuzları ArrayBuffer, boyutu varsayılan olarak 8 KiB olan ve 'de belirtilen' lerdir Buffer.poolSize. A için küçük boyutlu bir bellek parçası sağlanacağı Bufferzaman, son bellek havuzunun bunu işlemek için yeterli kullanılabilir belleğe sahip olup olmadığını kontrol eder; eğer öyleyse, bu bir oluşturur Bufferolduğunu “görünümler” bellek havuzunun verilen kısmi öbek, aksi takdirde yeni bir bellek havuzu ve benzeri yaratır.


Sen yatan erişebileceği ArrayBuffera Buffer. BufferBireyin buffermülkiyet (miras olduğunu Uint8Array) tutar. Bir “küçük” Buffer 'ın bufferözelliği bir olan ArrayBuffertüm bellek havuzu temsil ettiğini. Yani bu durumda, ArrayBufferve Bufferboyut olarak değişir.

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. Dolayısıyla, " görüntülediği " belleği çıkarmamız gerekir .

An ArrayBufferboyut olarak sabittir, bu yüzden parçanın bir kopyasını oluşturarak onu çıkarmamız gerekir. Bunu yapmak için, kullandığımız Buffer'ın byteOffsetmülkü ve lengthmülk , miras edilir Uint8Arrayve yöntemini bir bir parçası bir kopyasını oluşturur, . İng yöntem, burada esinlenerek @ZachB .ArrayBuffer.prototype.sliceArrayBufferslice()

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. Performans iyileştirme

Sonuçları salt okunur olarak kullanacaksanız veya giriş Bufferiçeriklerini değiştirmeniz uygunsa , gereksiz bellek kopyalamasını önleyebilirsiniz.

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
Bunların hepsi güzel ve güzel ... ama gerçekten OP'nin sorusuna cevap verdin mi?
Yaptıysan

Mükemmel cevap! İçinde obtain_arraybuffer: buf.buffer.subarrayvar gibi görünmüyor. buf.buffer.sliceBurada mı demek istedin ?
üretken günlük

@everydayproductive Teşekkürler. Düzenleme geçmişinde görebileceğiniz gibi, aslında kullandım ArrayBuffer.prototype.sliceve daha sonra olarak değiştirdim Uint8Array.prototype.subarray. Oh, ve ben yanlış yaptım. Muhtemelen o zamanlar biraz kafası karışmıştır. Şu anda senin sayende her şey yolunda.
Константин Ван


1

ArrayBufferBir daktilo olarak düşünebilirsiniz Buffer.

Bu ArrayBuffernedenle an, her zaman bir türe ihtiyaç duyar (sözde "Dizi Tampon Görünümü"). Tipik olarak, Dizi Arabellek Görünümünde bir Uint8Arrayveya türü vardır Uint16Array.

Renato Mangini'den ArrayBuffer ve String arasında dönüştürme hakkında iyi bir makale var .

Bir kod örneğinde (Node.js için) temel parçaları özetledim. Ayrıca, yazılan ArrayBufferve yazılmayan arasında nasıl dönüştürüleceğini gösterir 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

Yukarıdakileri bir Float64Array için denedim ve işe yaramadı.

Sonunda verinin gerçekten doğru parçalarda görünüme 'INTO' okunması gerektiğini fark ettim. Bu, kaynak Buffer'dan bir seferde 8 bayt okumak anlamına gelir.

Her neyse, sonuçta ben de bu ...

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'ın cevabının Uint8Array kullanmasının nedeni budur - öğelerin boyutundan bağımsızdır. Buffer.read*Yöntemler de, tüm yavaştır.
ZachB

Birden çok tiplenmiş dizi görünümü, aynı belleği kullanarak aynı ArrayBuffer'a başvurabilir. Buffer'daki her değer bir bayttır, bu nedenle onu öğe boyutu 1 bayt olan bir diziye koymanız gerekir. Martin'in yöntemini kullanabilir, ardından yapıcıdaki aynı arraybuffer'ı kullanarak yeni bir Float64Array oluşturabilirsiniz.
ZachB

0

Bu Proxy, arabelleği herhangi bir TypedArrays olarak kopyası olmadan ortaya çıkarır. :

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

Yalnızca LE üzerinde çalışır, ancak kolayca BE'ye taşınabilir. Ayrıca, bunun ne kadar verimli olduğunu asla test etmedim.


1
Bu bağlantı soruyu cevaplayabilirken, cevabın temel kısımlarını buraya eklemek ve referans için bağlantıyı sağlamak daha iyidir. Bağlantı verilen sayfa değişirse, yalnızca bağlantı yanıtları geçersiz hale gelebilir
koceeng

2
Sözlerim pek resmi gelmeyebilir, ancak çözümü yeniden oluşturmak için yeterli bilgi sağlıyor. Çözüm, yerel bir NodeJS Buffer'ı TypedArrays tarafından kullanılan alıcılar ve ayarlayıcılarla sarmak için JavaScript Proxy Nesnesine dayanır. Bu, Buffer örneğini Typed Array arabirimi gerektiren herhangi bir kitaplıkla uyumlu hale getirir. Orijinal göndericinin umduğu cevap budur, ancak akademik / kurumsal dilinize uymadığı için reddedebilirsiniz. Önemsiyor muyum bir bak.
Dlabz


-1

NodeJS, bir noktada (sanırım v0.6.x idi) ArrayBuffer desteği vardı. Burada base64 kodlama ve kod çözme için küçük bir kitaplık oluşturdum , ancak v0.7'ye güncellemeden sonra testler (NodeJS'de) başarısız oldu. Bunu normalleştiren bir şey yaratmayı düşünüyorum, ancak o zamana kadar Node'un yerelinin Bufferkullanılması gerektiğini düşünüyorum.


-6

Düğümümü zaten Sürüm 5.0.0'a güncelledim ve bununla çalışıyorum:

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 disk imajımı kontrol etmek için kullanıyorum.


Bu, Buffer / ArrayBuffer'a / Arabelleğe dönüştürme için genel bir yöntem değil, özelleştirilmiş (ve yavaş) serileştirme tabanlı bir yöntem gibi görünüyor?
ZachB

@ZachB, V5.0.0 + [yalnızca] = = için genel bir yöntemdir.
Miguel Valentine

toArrayBuffer(new Buffer([1,2,3]))-> ['01', '02', '03']- bu tamsayılar / baytlar değil, dizelerden oluşan bir dizi döndürür.
ZachB

@ZachB dönüş dizisi -> dönüş listesi. int-> dizeyi stdout için düzeltiyorum
Miguel Valentine

Bu durumda, aynı şey stackoverflow.com/a/19544002/1218408 gerekli bayt ofset kontrolleri olmadan hala ve stackoverflow.com/a/31394257/1218408 .
ZachB
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.