node buffer

Buffer結(jié)構(gòu)

  • 類似Array,為16進(jìn)制的兩位數(shù),即占一個(gè)字節(jié)
  • js與c++結(jié)合的模塊,內(nèi)存由c++申請(qǐng),js分配。因?yàn)関8垃圾回收影響性能
  • node啟動(dòng)時(shí)就加載,放在全局對(duì)象global
var buf = new Buffer(100);
console.log(buf.length); // => 100

如給buffer賦值數(shù)字,則范圍在0-255,否則負(fù)數(shù)就加256,過大就減256

內(nèi)存使用slab分配機(jī)制,動(dòng)態(tài)內(nèi)存管理,包含三種狀態(tài)(full,partial,empty)。Node以8KB為界限區(qū)分Buffer是大對(duì)象還是小對(duì)象(Buffer.poolSize=8*1024),即8kb為slab單元大小,js以它為單元分配內(nèi)存

分配小Buffer對(duì)象

  • 小于8kb

使用局部變量pool,讓處于分配狀態(tài)的slab單元指向它

var pool;

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

新建小buffer時(shí),如果還沒pool,就創(chuàng)建一個(gè)slab指向它,當(dāng)前的buffer對(duì)象的parent指向slab

this.parent = pool; //當(dāng)前的buffer對(duì)象的parent指向slab
this.offset = pool.used;//slab開始使用的位置
pool.used += this.length;//slab已使用量
if (pool.used & 7) pool.used = (pool.used + 8) & ~7;

此時(shí)slab狀態(tài)為partial,在創(chuàng)建buffer時(shí)會(huì)判斷次slab是否夠用,如果不夠,就構(gòu)建新的slab,原來的slab剩余的空間浪費(fèi),如果不釋放就占據(jù)8kb

分配大Buffer

直接分配一個(gè)SlowBuffer對(duì)象作為slab單元,并且獨(dú)占

// Big buffer,just alloc one  SlowBuffer由c++定義,勿直接操作它
this.parent=new SlowBuffer(this.length);this.offset=0;

Buffer對(duì)象是js層面的,能被v8標(biāo)記回收,但其內(nèi)部parent指向SlowBuffer,由c++提供的Buffer,所以內(nèi)存由c++提供,js只是使用它,對(duì)于小buffer的頻繁操作,使用slab機(jī)制來先申請(qǐng)后分配,減少內(nèi)存申請(qǐng)的系統(tǒng)調(diào)用,對(duì)于大buffe就直接用C++提供內(nèi)存

Buffer轉(zhuǎn)換

  • 支持的字符串編碼 ASCII,UTF-8,UTF-16LE/UCS-2,Base64,Binary,Hex

字符串與Buffer的轉(zhuǎn)換

字符串轉(zhuǎn)buffer,使用構(gòu)造函數(shù)new Buffer(str,[encoding]);默認(rèn)UTF-8

一個(gè)Buffer對(duì)象可以存多種編碼類型的字符串轉(zhuǎn)碼的值

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

buffer轉(zhuǎn)字符串

buf.toString([encoding], [start], [end]) //encoding默認(rèn)UTF-8,配合start和end實(shí)現(xiàn)局部轉(zhuǎn)換

Buffer不支持的編碼類型

  • 使用Buffer.isEncoding(encoding)判斷

iconv 通過c++調(diào)用libiconv
iconv-lite使用純js實(shí)現(xiàn),但基于v8高性能,少了c++到j(luò)s的轉(zhuǎn)換,所以比C++實(shí)現(xiàn)好

var iconv = require("iconv-lite");var str = iconv.decode(buf,"win1251");
var buf = iconv.encode('Sample input string', 'win1251');

對(duì)無法轉(zhuǎn)換的內(nèi)容會(huì)降級(jí)處理,輸出部分或者?

Buffer拼接

var fs = require('fs');
var rs = fs.createReadStream('test.md');
var data = '';
rs.on("data", function(chunk) {
    data += chunk; //等價(jià)于data = data.toString() + chunk.toString();此處對(duì)寬字節(jié)的中文可能造成亂碼,即字節(jié)沒讀全就轉(zhuǎn)碼
});
rs.on("end", function() {
    console.log(data);
});

解決亂碼問題

  • 在調(diào)用setEncoding()時(shí),可讀流對(duì)象在內(nèi)部設(shè)置一個(gè)decoder對(duì)象

req.setEncoding('utf8');

var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8');
var buf1 = new Buffer([0xE5, 0xBA, 0x8A, 0xE5, 0x89, 0x8D, 0xE6, 0x98, 0x8E, 0xE6, 0x9C]);
console.log(decoder.write(buf1));
// => 床前明
var buf2 = new Buffer([0x88, 0xE5, 0x85, 0x89, 0xEF, 0xBC, 0x8C, 0xE7, 0x96, 0x91, 0xE6]);
console.log(decoder.write(buf2));
// =>月光,凝

StringDecoder在得到編碼后,知道寬字節(jié)在utf-8下占3個(gè)字節(jié),所以在處理末尾不全的字節(jié)時(shí),會(huì)保留到第二次write().目前只能處理UTF-8、Base64和UCS-2/UTF-16LE

正確拼接Buffer

var chunks = [];
var size = 0;
res.on('data', function(chunk) {
    chunks.push(chunk);
    size += chunk.length;
});
res.on('end', function() {
    var buf = Buffer.concat(chunks, size);
    var str = iconv.decode(buf, 'utf8');
    console.log(str);
});

用數(shù)組來儲(chǔ)存接收的所有Buffer片段并記錄總長(zhǎng)度,然后調(diào)用Buffer.concat()-->

Buffer.concat = function(list, length) {
    if (!Array.isArray(list)) {
        throw new Error('Usage: Buffer.concat(list, [length])');
    }
    if (list.length === 0) {
        return new Buffer(0);
    } else if (list.length === 1) {
        return list[0];
    }
    if (typeof length !== 'number') {
        length = 0;
        for (var i = 0; i < list.length; i++) {
            var buf = list[i];
            length += buf.length;
        }
    }
    var buffer = new Buffer(length);
    var pos = 0;
    for (var i = 0; i < list.length; i++) {
        var buf = list[i];
        buf.copy(buffer, pos);
        pos += buf.length;
    }
    return buffer;
};

Buffer與性能

Buffer廣泛應(yīng)用于文件I/O和網(wǎng)絡(luò)I/O,尤其在網(wǎng)絡(luò)傳輸,使用Buffer比直接使用字符串要性能要高很多。在web開發(fā)中對(duì)于靜態(tài)內(nèi)容可以預(yù)先轉(zhuǎn)成buffer,在不需要改變內(nèi)容時(shí),只讀取buffer,不做轉(zhuǎn)換

文件讀取

fs.createReadStream()先在內(nèi)存中準(zhǔn)備一段buffer,然后在fs.read()讀取時(shí)逐步將磁盤中的字節(jié)復(fù)制到buffer,讀完一次就用slice()從buffer取出部分作為小buffer通過data事件傳給調(diào)用方。Buffer用完會(huì)重新分配

fs.createReadStream(path, opts)
//參數(shù)
{
    flags: 'r',
    encoding: null,
    fd: null,
    mode: 0666,
    highWaterMark: 64 * 1024 // 每次讀取的長(zhǎng)度
}

重新分配

var pool;//常駐內(nèi)存

function allocNewPool(poolSize) {
    pool = new Buffer(poolSize);
    pool.used = 0;
}
//當(dāng)pool剩余數(shù)量小于128(kMinPoolSpace)字節(jié)時(shí),會(huì)重新分配
if (!pool || pool.length - pool.used < kMinPoolSpace) { 
// discard the old pool
    pool = null;
    allocNewPool(this._readableState.highWaterMark);
}


highWaterMark的大小對(duì)性能的影響有:buffer內(nèi)存的分配和使用、系統(tǒng)調(diào)用次數(shù);

  • 文件流讀取基于buffer,buffer基于slowbuffer,文件小于8kb可能造成slab浪費(fèi)
  • fs.createReadStream()內(nèi)部使用fs.read(),會(huì)引起系統(tǒng)對(duì)磁盤的調(diào)用,highWaterMark的大小決定調(diào)用次數(shù)和data事件次數(shù)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容