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ù)