Node.js Buffer解讀

Randal發表於2018-05-17

Buffer是什麼?

Buffer作為存在於全域性物件上,無需引入模組即可使用,你絕對不可以忽略它。 可以理解Buffer是在記憶體中開闢的一片區域,用於存放二進位制資料。Buffer所開闢的是堆外記憶體。

Buffer的應用場景有哪些?

怎麼理解流呢?流是資料的集合(與資料、字串類似),但是流的資料不能一次性獲取到,資料也不會全部load到記憶體中,因此流非常適合大資料處理以及斷斷續續返回chunk的外部源。流的生產者與消費者之間的速度通常是不一致的,因此需要buffer來暫存一些資料。buffer大小通過highWaterMark引數指定,預設情況下是16Kb。

儲存需要佔用大量記憶體的資料

Buffer 物件佔用的記憶體空間是不計算在 Node.js 程式記憶體空間限制上的,所以可以用來儲存大物件,但是物件的大小還是有限制的。一般情況下32位系統大約是1G,64位系統大約是2G。

如何建立Buffer

除了流自動隱式建立Buffer之外,也可以手動建立Buffer,方式如下:

Buffer中儲存的資料已確定

Buffer.from(obj) // obj支援的型別string, buffer, arrayBuffer, array, or array-like object

注意:Buffer.from不支援傳入數字,如下所示:

Buffer.from(1234);

buffer.js:208
    throw new errors.TypeError(
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "value" argument must not be of type number. Received type number
    at Function.from (buffer.js:208:11)
    ...
    
複製程式碼

若要傳入數字可以採用傳入陣列的方式:

const buf = Buffer.from([1, 2, 3, 4]);
console.log(buf); //  <Buffer 01 02 03 04>
複製程式碼

但是這種方式存在一個問題,當存入不同的數值的時候buffer中記錄的二進位制資料會相同,如下所示:

const buf2 = Buffer.from([127, -1]);
console.log(buf2);     // <Buffer 7f ff>

const buf3 = Buffer.from([127, 255]);
console.log(buf3);    // <Buffer 7f ff>

console.log(buf3.equals(buf2));  // true
複製程式碼

當要記錄的一組數全部落在0到255(readUInt8來讀取)這個範圍, 或者全部落在-128到127(readInt8來讀取)這個範圍那麼就沒有問題,否則的話就強烈不推薦使用Buffer.from來儲存一組數。因為不同的數字讀取時應該呼叫不同的方法。

Buffer儲存資料未確定

Buffer.alloc、Buffer.allocUnsafe、Buffer.allocUnsafeSlow

Buffer.alloc會用0值填充已分配的記憶體,所以相比後兩者速度上要慢,但是也較為安全。當然也可以通過--zero-fill-buffers flag使allocUnsafe、allocUnsafeSlow在分配完記憶體後也進行0值填充。

node --zero-fill-buffers index.js
複製程式碼

當分配的空間小於4KB的時候,allocUnsafe會直接從之前預分配的Buffer裡面slice空間,因此速度比allocUnsafeSlow要快,當大於等於4KB的時候二者速度相差無異。

// 分配空間等於4KB
function createBuffer(fn, size) {
  console.time('buf-' + fn);
  for (var i = 0; i < 100000; i++) {
    Buffer[fn](size);
  }
  console.timeEnd('buf-' + fn);
}
createBuffer('alloc', 4096);
createBuffer('allocUnsafe', 4096);
createBuffer('allocUnsafeSlow', 4096);

// 輸出
buf-alloc:           294.002ms
buf-allocUnsafe:     224.072ms
buf-allocUnsafeSlow: 209.22ms
複製程式碼
function createBuffer(fn, size) {
  console.time('buf-' + fn);
  for (var i = 0; i < 100000; i++) {
    Buffer[fn](size);
  }
  console.timeEnd('buf-' + fn);
}
createBuffer('alloc', 4095);
createBuffer('allocUnsafe', 4095);
createBuffer('allocUnsafeSlow', 4095);
// 輸出
buf-alloc:           296.965ms
buf-allocUnsafe:     135.877ms
buf-allocUnsafeSlow: 205.225ms
複製程式碼

需要謹記一點:new Buffer(xxxx) 方式已經不推薦使用了

Buffer使用

buffer轉字串

const buf = Buffer.from('test');
console.log(buf.toString('utf8'));                 // test
console.log(buf.toString('utf8', 0, 2));           // te
複製程式碼

buffer轉json

const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
console.log(buf.toJSON());    // { type: 'Buffer', data: [ 1, 2, 3, 4, 5 ] }
複製程式碼

buffer裁剪,裁剪後返回的新的buffer與原buffer指向同一塊記憶體

buf.slice([start[, end]])
start 起始位置
end 結束位置(不包含)

示例:

var buf1 = Buffer.from('test');
var buf2 = buf1.slice(1, 3).fill('xx');
console.log("buf2 content: " + buf2.toString()); // xx
console.log("buf1 content: " + buf1.toString()); // txxt
複製程式碼

buffer拷貝,buffer與陣列不同,buffer的長度一旦確定就不再變化,因此當拷貝的源buffer比目標buffer大時只會複製部分的值

buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])

示例:
var buf1 = Buffer.from('abcdefghijkl');
var buf2 = Buffer.from('ABCDEF');

buf1.copy(buf2, 1);
console.log(buf2.toString()); //Abcdef
複製程式碼

buffer相等判斷,比較的是二進位制值

buf.equals(otherBuffer)

示例:
const buf1 = Buffer.from('ABC');
const buf2 = Buffer.from('414243', 'hex'); 
console.log(buf1.equals(buf2));    // true
複製程式碼

除了equals之外,compare其實也可以用於判斷是否相等(當結果為0則相等),不過compare更主要的作用是用於對陣列內的buffer例項排序。

buffer是否包含特定值

buf.includes(value[, byteOffset][, encoding])
buf.indexOf(value[, byteOffset][, encoding])

示例:
const buf = Buffer.from('this is a buffer');
console.log(buf.includes('this'));  // true
console.log(buf.indexOf('this'));  // 0
複製程式碼

寫入讀取數值

寫入方法:

位數固定且超過1個位元組的: write{Double| Float | Int16 | Int32| UInt16 | UInt32 }{BE|LE}(value, offset)

位數不固定的: write{Int | UInt}{BE | LE}(value, offset, bytelength) //此方法提供了更靈活的位數表示資料(比如3位、5位)

位數固定是1個位元組的: write{Int8 | Unit8}(value, offset)

讀取方法:

位數固定且超過1個位元組的: read{Double| Float | Int16 | Int32 | UInt16 | UInt32 }{BE|LE}(offset)

位數不固定的: read{Int | UInt}{BE | LE}(offset, byteLength)

位數固定是1個位元組的: read{Int8 | Unit8}(offset)

Double、Float、Int16、Int32、UInt16、UInt32既確定了表徵數字的位數,也確定了是否包含負數,因此定義了不同的資料範圍。同時由於表徵數字的位數都超過8位,無法用一個位元組來表示,因此就涉及到了計算機的位元組序區分(大端位元組序與小端位元組序)

關於大端小端的區別可以這麼理解:數值的高位在buffer的起始位置的是大端,數值的低位buffer的起始位置則是小端

const buf = Buffer.allocUnsafe(2);
buf.writeInt16BE(256, 0)  
console.log(buf);           // <Buffer 01 00> 
buf.writeInt16LE(256, 0)
console.log(buf);           // <Buffer 00 01>
複製程式碼

https://tool.lu/hexconvert/ 這裡可以檢視數值的不同進位制之間的轉換,如果是大端的話,則直接按順序(0100)拼接16進位制即可,如果是小端則需要調換一下順序才是正確的表示方式。

buffer合併

Buffer.concat(list[, totalLength]) //totalLength不是必須的,如果不提供的話會為了計算totalLength會多一次遍歷

const buf1 = Buffer.from('this is');
const buf2 = Buffer.from(' funny');
console.log(Buffer.concat([buf1, buf2], buf1.length + buf2.length));
// <Buffer 74 68 69 73 20 69 73 20 66 75 6e 6e 79>
複製程式碼

清空buffer

清空buffer資料最快的辦法是buffer.fill(0)

buffer模組與Buffer的關係

Buffer是全域性global上的一個引用,指向的其實是buffer.Buffer

 const buffer = require('buffer');
 console.log(buffer.Buffer === Buffer); //true
複製程式碼

buffer模組上還有其他一些屬性和方法

const buffer = require('buffer');
console.log(buffer);
{ Buffer:
   { [Function: Buffer]
     poolSize: 8192,
     from: [Function: from],
     alloc: [Function: alloc],
     allocUnsafe: [Function: allocUnsafe],
     allocUnsafeSlow: [Function: allocUnsafeSlow],
     isBuffer: [Function: isBuffer],
     compare: [Function: compare],
     isEncoding: [Function: isEncoding],
     concat: [Function: concat],
     byteLength: [Function: byteLength],
     [Symbol(node.isEncoding)]: [Function: isEncoding] },
  SlowBuffer: [Function: SlowBuffer],
  transcode: [Function: transcode],
  INSPECT_MAX_BYTES: 50,
  kMaxLength: 2147483647,
  kStringMaxLength: 1073741799,
  constants: { MAX_LENGTH: 2147483647, MAX_STRING_LENGTH: 1073741799 } }
複製程式碼

上面的kMaxLength與MAX_LENGTH代表了新建buffer時記憶體大小的最大值,當超過限制值後就會報錯

32為機器上是(2^30)-1(~1GB)

64位機器上是(2^31)-1(~2GB)

Buffer釋放

我們無法手動對buffer例項進行GC,只能依靠V8來進行,我們唯一能做的就是解除對buffer例項的引用

參考資料

cenalulu.github.io/linux/chara…

www.ruanyifeng.com/blog/2007/1…

medium.freecodecamp.org/do-you-want…

www.barretlee.com/blog/2017/0…

medium.freecodecamp.org/node-js-str…

www.runoob.com/nodejs/node…

相關文章