node之Buffer的使用

一 Buffer概念

JavaScript在前端中處理字符串相關(guān)的API非常強(qiáng)大,好用,但是Node還需要額外處理網(wǎng)絡(luò)、文件中的一些二進(jìn)制數(shù)據(jù),ECMAScript標(biāo)準(zhǔn)中沒有提供這樣的API。

Buffer是Node提供的一個(gè)全新對(duì)象,非常類似JS中的Array,但是主要用來(lái)操作字節(jié)。Buffer在Node中由兩部分源碼實(shí)現(xiàn):

  • Buffer/SlowBuffer:js核心模塊,主要用于實(shí)現(xiàn)Buffer在業(yè)務(wù)上的一些API
  • node_buffer:C++內(nèi)建模塊,主要用于實(shí)現(xiàn)字節(jié)處理的性能相關(guān)部分

注意:Buffer由于在Node中使用場(chǎng)景非常廣泛,所以在Node進(jìn)程啟動(dòng)時(shí)就已經(jīng)加載,位于全局對(duì)象global中。

二 Buffer的基本使用

可以將Buffer理解為Node擴(kuò)充的數(shù)據(jù)類型,其作用類似Array,用于操作二進(jìn)制數(shù)據(jù)。

Buffer的創(chuàng)建:

// new Buffer() 的方式由于安全原因已經(jīng)過(guò)期,Node8之后推薦使用以下方式創(chuàng)建
let buf = Buffer.from('test','utf-8');                    
console.log(bf);                                    // 輸出 <Buffer 74 65 73 74>

for(let i = 0; i < bf.length; i++){                 //此length長(zhǎng)度和字符串的長(zhǎng)度有區(qū)別,指buffer的bytes大小
    console.log(bf[i].toString(16));                // buffer[index]:獲取或設(shè)置在指定index索引未知的8位字節(jié)內(nèi)容
    console.log(String.fromCharCode(bf[i]));        //依次輸出 t e s t
    console.log(bf.toString());                     //輸出 test  ,可選參數(shù)是 [encoding, start, end],默認(rèn)使用UTF-8
}

Node在文件、網(wǎng)絡(luò)操作中,如果沒有顯示聲明編碼格式,默認(rèn)返回的數(shù)據(jù)類型都是Buffer,比如readFile回調(diào)中的data。

注意:在ES6中增加了ArrayBuffer類型,Node中可以直接使用

Buffer的實(shí)例方法:

buf.write(string,[offset],[length],[encoding]):根據(jù)參數(shù)offset,將參數(shù)string數(shù)據(jù)寫入buffer
buf.toString([encoding],[length]):返回一個(gè)解碼的string類型
buf.toJSON():返回一個(gè)JSON表示的Buffer實(shí)例,JSON.stringify將會(huì)默認(rèn)調(diào)用來(lái)字符串序列化這個(gè)Buffer實(shí)例
buf.slice([start],[end]):返回一個(gè)新的buffer,這個(gè)buffer和老的buffer引用相同的內(nèi)存地址
buf.copy(targetBuffer,[targetStart],[sourceStart],[sourceEnd]):進(jìn)行buffer的拷貝,拷貝不會(huì)影響老的buffer。

Buffer的靜態(tài)方法:

Buffer.isBuffer(buf);                   // 判斷是不是Buffer
Buffer.byteLength(str);                 // 獲取字節(jié)長(zhǎng)度,第二個(gè)參數(shù)為字符集,默認(rèn)utf8
Buffer.concat(list[, totalLength])      // Buffer的拼接

三 Buffer的轉(zhuǎn)換

2.1 字符串轉(zhuǎn)Buffer

Buffer對(duì)象可以與字符串之間相互轉(zhuǎn)換,如下所示:

new Buffer(str, [encoding]);            // 可選參數(shù)編碼格式若不傳入,則默認(rèn)按照UTF-8編碼進(jìn)行轉(zhuǎn)碼和存儲(chǔ)

一個(gè)Buffer對(duì)象可以存儲(chǔ)不同編碼類型的字符串轉(zhuǎn)碼的值,調(diào)用write()方法可以實(shí)現(xiàn)該目標(biāo),代碼如下:

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

由于可以不斷寫入內(nèi)容到Buffer對(duì)象中,并且每次寫入可以指定編碼,所以Buffer對(duì)象中可以存在多種編碼轉(zhuǎn)換后的內(nèi)容,需要小心的是,每種編碼所用的字節(jié)長(zhǎng)度不同,將buffer反轉(zhuǎn)回字符串時(shí)需要謹(jǐn)慎處理。

2.2 Buffer轉(zhuǎn)字符串

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

2.3 Buffer不支持的編碼類型

Node的Buffer不支持中國(guó)的GBK,GB2312,BUG-5等編碼格式。判斷Buffer是否支持該編碼格式:

Buffer.isEncoding(encodibg);        // 返回 true、false

對(duì)于不支持的編碼格式,Node有第三方模塊如 iconv 和 iconv-liten。

三 Buffer亂碼

3.1 亂碼的產(chǎn)生

在Buffer使用場(chǎng)景中,通常是以一段一段的方式傳輸,常見從輸入流中讀取內(nèi)容的示例如下:

var fs = require('fs');

var rs = fs.createReadStream('./demo.md');
var data = '';

rs.on("data", function(chunk) {
    data += chunk;
});

rs.end("end", function(){
    console.log(data);
});

上述代碼在讀取全英文格式內(nèi)容時(shí),不會(huì)有任何問(wèn)題,但是一旦輸入流中存在寬字節(jié)編碼,就會(huì)產(chǎn)生亂碼問(wèn)題。問(wèn)題來(lái)自于data += chunk,該句隱藏了 toString()操作,其內(nèi)部等價(jià)于:

data = data.toString() + chunk.toString();

下面模擬寬字節(jié)文字讀取場(chǎng)景:

var fs = require('fs');

var buf = Buffer.from("白銀之手騎士團(tuán)");

// <Buffer e7 99 bd e9 93 b6 e4 b9 8b e6 89 8b e9 aa 91 e5 a3 ab e5 9b a2 ef bc 81>
console.log("buf:", buf);
console.log("buf.length: ", buf.length);                        // 21
console.log("start: ", buf.toString("UTF-8", 0, 3));             // 白  e7 99 bd     
console.log("start: ", buf.toString("UTF-8", 3, 6));             // 銀  e9 93 b6
console.log("start: ", buf.toString("UTF-8", 6, 9));             // 之  e4 b9 8b,e6 89 8b,e9 aa 91,e5 a3 ab,e5 9b a2,ef bc 81

var data = "";
var rs = fs.createReadStream("./demo.txt", {highWaterMark: 4});
rs.on("data", function(chunk) {
    data += chunk;
});
rs.on("end", function(){
    console.log("流式讀?。?, data);                        // 白?????手騎?????
});

在上述案例中,每3個(gè)長(zhǎng)度能夠讀取到一個(gè)漢字,但是在使用流式讀取時(shí),每4個(gè)長(zhǎng)度讀取一次,在第一讀取時(shí),就會(huì)讀取到多余的數(shù)據(jù)了,也即輸出了 白?,在第4次讀取時(shí),正好又讀取了原始數(shù)據(jù)的存儲(chǔ)要求,輸出了?手,依次類推。

3.2 亂碼解決

流式讀取可以設(shè)置編碼:

var rs = fs.createReadStream("./demo.txt", {highWaterMark: 4});
rs.setEncoding('utf8');

此時(shí)程序就能正常輸出數(shù)據(jù)!但是這并不是直接說(shuō)明了輸出沒有收到Buffer大小的影響。在實(shí)際運(yùn)行過(guò)程中,無(wú)論如何設(shè)置編碼,觸發(fā)的data事件次數(shù)都仍然是相同的。但是在每次data事件都會(huì)額外通過(guò)一個(gè)decoder對(duì)象對(duì)Buffer進(jìn)行轉(zhuǎn)換到字符串的解碼,然后傳遞給調(diào)用者。而這個(gè)decoder對(duì)象正是 setEncoding()方法時(shí)在可讀流對(duì)象內(nèi)部設(shè)置的。此時(shí)data收到的不再是原始的Buffer對(duì)象。decoder對(duì)象會(huì)被未轉(zhuǎn)碼的部分保留在StringDecode實(shí)例內(nèi)部,再下一次write的時(shí)候,會(huì)將上次的剩余字節(jié)和后續(xù)的新讀入的字節(jié)進(jìn)行組合!

setEncding只能解決UTF-8,Base64等帶來(lái)的編碼問(wèn)題,沒有從根本上解決問(wèn)題。正確的Buffer拼接方式應(yīng)該是用一個(gè)數(shù)組來(lái)存儲(chǔ)接收到的所有Buffer片段并記錄下所有的片段總長(zhǎng)度,然后調(diào)用Buffer.concat()方法生成一個(gè)合并的Buffer對(duì)象。

fs.createReadStream("./test.txt",{highWaterMark: 10});

var dataArr = [];

rs.on("data", function(chunk){
    dataArr.push(chunk);
});

rs.on("end", function(){
    var buf = Buffer.concat(dataArr);
    console.log(buf.toString());
});

Buffer.concat()方法封裝了從小Buffer對(duì)象向大Buffer對(duì)象復(fù)制過(guò)程:

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;
};
?著作權(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)容