Buffer學習筆記.

前言

JavaScript 對於字符串的操做十分便捷,不管是單字節字符仍是寬字節字符,都會認爲是一個字符。對字符串的簡單操做和DOM操做基本上已經能夠知足前端工程需求,但Node不少時候須要處理文件和網絡I/O,就須要處理大量的二進制數據。因此,在Node中就定義了一個Buffer類,該類用來建立一個專門存放二進制數據的緩存區。本文是《深刻淺出nodejs》相關內容的一個總結,是一篇讀書筆記,僅供之後查閱。前端

在 Node.js 中,Buffer 類是隨 Node 內核一塊兒發佈的核心庫。Buffer 庫爲 Node.js 帶來了一種存儲原始數據的方法,可讓 Node.js 處理二進制數據,每當須要在 Node.js 中處理I/O操做中移動的數據時,就有可能使用 Buffer 庫。原始數據存儲在 Buffer 類的實例中。一個 Buffer 相似於一個整數數組,但它對應於 V8 堆內存以外的一塊原始內存。node

1.Buffer結構

 Buffer是一個相似與Array的對象,但它主要是用於字節操做。也擁有一些相似於Array具備的方法,可使用這些方法進行字節處理。數組

1.1模塊結構

Buffer是一個JavaScript與C++結合的模塊,它將性能相關部分使用C++實現,將非性能相關的部分用JavaScript實現。緩存

Buffer所佔用的內存不是經過V8分配的,屬於堆外內存。因爲V8垃圾回收性能的影響,將經常使用的操做對象用更高效和專用的內存分配回收策略來管理是個不錯的思路。網絡

Buffer是Node的一個核心模塊,在進程啓動時已經加載,並放置在全局對象中,使用Buffer時,不需require()引入,可直接使用。性能

1.2Buffer對象

Buffer對象相似於數組,它的元素爲16進制的兩位數(如:1F,0~255)。示例以下:ui

不一樣編碼的字符串佔用的元素個數各不相同,ASCII編碼的字符佔用一個元素,漢字佔用兩個元素。this

當新建一個Buffer,而沒有寫入數據時,訪問其中的元素,會獲得一個0~255的隨機值。編碼

咱們對Buffer中的一個元素賦值的話,它會默認的轉爲一個0~255的值,它的轉換規則以下:spa

  • 1.給元素賦值小於0,就將該值逐次加256,直到獲得一個0~255的整數;
  • 2.給元素賦值大於255,就逐次減256,直到獲得一個0~255的整數;
  • 3.給元素賦值是小數,捨棄小數部分,只保留整數部分。

1.3Buffer的內存分配

處理大量的字節數據不能採用須要一點內存就向系統申請一點內存的方式,這會形成很大的內存申請的系統調用,對操做系統有必定壓力。爲此Node在內存的使用上應用的是在C++層面申請,在JavaScript中分配的策略。

爲了高效的使用申請來的內存,Node採用了slab分配機制。slab就是一個申請好的固定大小的內存區域,有如下三種狀態:

  • full:徹底分配狀態;
  • partial:部分分配狀態;
  • empty:沒有分配狀態。

在node中,須要一個Buffer對象,能夠經過如下方式指定對象的大小:

new Buffer(size)

Node以8KB爲分界來區分Buffer是大對象仍是小對象。在JavaScript層面,以它做爲單位單元進行內存的分配。

分配小Buffer對象

 若是指定Buffer的大小少於8KB,Node會按照小對象的方式進行分配。Buffer的分配過程當中主要使用一個局部變量pool做爲中間處理對象,處於分配狀態的slab單元都指向它。如下是分配一個全新的slab單元的操做,它會將新申請的SlowBuffer對象指向它:

var pool;

function allocPool() {
    pool = new SlowBuffer(Buffer.poolSize);  
    pool.used = 0;  
}

slab處於empty狀態。

構造小Buffer對象的代碼以下:

new Buffer(1024);

此次構造將會去檢查pool對象,若是pool沒有被建立,將會建立一個新的slab單元指向它:

if(!pool || pool.length - pool.used < this.length) {
    allocPool();
}

同時當前Buffer對象的parent屬性指向該slab,並記錄下是從這個slab的哪一個位置(offset)開始使用的,slab對象自身也記錄被使用了多少字節,代碼以下:

this,parent = pool;
this.offset = pool.used;
pool.used += this.length;
if(pool.used & 7) {
    pool.used = (pool.used + 8) & ~7;
}

這時候的slab狀態爲partial。當再次建立一個Buffer對象時,構造過程當中將會判斷這個slab的剩餘空間是否足夠。若是足夠就使用剩餘空間,並更新slab的分配狀態。若是剩餘空間不足,將會從新構造slab,原slab中剩餘的空間會形成浪費。

這就帶來了一個問題,一個slab可能分配給多個Buffer對象使用,只有這些Buffer對象在做用域釋放並均可以回收時,slab的8KB空間纔會被回收。

分配大Buffer對象

若是須要超過8KB的Buffer對象,將會直接分配一個SlowBuffer對象做爲slab單元,這個單元將會被這個大Buffer對象所獨佔。

上面提到的Buffer對象都是JavaScript層面上的,可以被V8的垃圾回收標記回收。但其內部的parent屬性指向的SlowBuffer對象卻來自於Node自身C++中的定義,是C++層面上的Buffer對象,因此內存不在V8的堆中。

2.Buffer的轉換

Buffer對象能夠直接與字符串之間相互轉換,目前支持的字符串編碼類型有如下幾種:

  • ASCII
  • UTF-8
  • UTF-16LE/UCS-2
  • Base64
  • Binary
  • Hex

字符串轉Buffer:

new Buffer(str, [encoding]);

當encoding不傳入時,默認爲utf-8。一個Buffer對象能夠存儲不一樣編碼類型的字符串轉碼的值,調用write方法能夠實現它:

buf.write(string, [offset], [length], [encoding]);

須要特別注意的是,不一樣編碼所用的字節長度不一樣,將Buffer反轉回來字符串時須要謹慎處理。

Buffer轉字符串:

buf.toString([encoding], [start], [end]);

當encoding不傳入時,默認爲utf-8。能夠經過設置encoding,start,end這3個參數實現總體或局部的轉換。

3.Buffer的拼接

Buffer在使用時,一般是一段一段的方式傳輸。如下是常見的從輸入流中讀取內容的示例代碼:

var fs = require('fs');

var rs = fs.createReadStream('test.md');
var data = '';
rs.on('data', function (chunk){
    data += chunk;
});
rs.on('end', function (){
    console.log(data);
});
相關文章
相關標籤/搜索