深刻淺出Node.js(六):Buffer那些事兒

做爲前端的JSer,是一件很是幸福的事情,由於在字符串上歷來沒有出現過任何糾結的問題。咱們來看看PHP對字符串長度的判斷結果: php

<? php 
echo strlen("0123456789");     
echo strlen("零一二三四五六七八九");     
echo mb_strlen("零一二三四五六七八九", "utf-8");      
echo "\n";

以上三行判斷分別返回十、30、10。對於中國人而言,strlen這個方法對於Unicode的判斷結果是很是讓人疑惑。而看看JavaScript中對字符串長度的判斷,就知道這個length屬性對調用者而言是多麼友好。 html

console.log("0123456789".length); // 10
console.log("零一二三四五六七八九".length); /10
console.log("\u00bd".length); // 1

儘管在計算機內部,一箇中文字和一個英文字佔用的字節位數是不一樣的,但對於用戶而言,它們擁有相同的長度。我認爲這是JavaScript中 String處理得精彩的一個點。正是因爲這個緣由,全部的數據從後端傳輸到前端被調用時,都是這般友好的字符串。因此對於前端工程師而言,他們是沒有字 符串Buffer的概念的。若是你是一名前端工程師,那麼今後在與Node.js打交道的過程當中,必定要當心Buffer啦,由於它比傳統的String 要調皮一點。 前端

你該當心Buffer啦

像許多計算機的技術同樣,都是從國外傳播過來的。那些以英文做爲母語的傳道者們應該沒有考慮過英文之外的使用者,因此你有可能看到以下這樣一段代碼在向你描述如何在data事件中鏈接字符串。 node

var fs = require('fs');
var rs = fs.createReadStream('testdata.md');
var data = '';
rs.on("data", function (trunk){
    data += trunk;
});
rs.on("end", function () {
    console.log(data);
});

若是這個文件讀取流讀取的是一個純英文的文件,這段代碼是可以正常輸出的。可是若是咱們再改變一下條件,將每次讀取的buffer大小變成一個奇數,以模擬一個字符被分配在兩個trunk中的場景。 git

var rs = fs.createReadStream('testdata.md', {bufferSize: 11});

咱們將會獲得如下這樣的亂碼輸出: github

事件循���和請求���象構成了Node.js���異步I/O模型的���個基本���素,這也是典���的消費���生產者場景。

形成這個問題的根源在於data += trunk語句裏隱藏的錯誤,在默認的狀況下,trunk是一個Buffer對象。這句話的實質是隱藏了toString的變換的: npm

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

因爲漢字不是用一個字節來存儲的,致使有被截破的漢字的存在,因而出現亂碼。解決這個問題有一個簡單的方案,是設置編碼集: 後端

var rs = fs.createReadStream('testdata.md', {encoding: 'utf-8', bufferSize: 11});

這將獲得一個正常的字符串響應: 網絡

事件循環和請求對象構成了Node.js的異步I/O模型的兩個基本元素,這也是典型的消費者生產者場景。

遺憾的是目前Node.js僅支持hex、utf八、ascii、binary、base6四、ucs2幾種編碼的轉換。對於那些由於歷史遺留問題依舊還生存着的GBK,GB2312等編碼,該方法是無能爲力的。 前端工程師

有趣的string_decoder

在這個例子中,若是仔細觀察,會發現一件有趣的事情發生在設置編碼集以後。咱們提到data += trunk等價於data = data.toString() + trunk.toString()。經過如下的代碼能夠測試到一個漢字佔用三個字節,而咱們按11個字節來截取trunk的話,依舊會存在一個漢字被分割在兩個trunk中的情景。

console.log("事件循環和請求對象".length);
console.log(new Buffer("事件循環和請求對象").length);

按照猜測的toString()方式,應該返回的是事件循xxx和請求xxx象纔對,其中「環」字應該變成亂碼纔對,可是在設置了encoding(默認的utf8)以後,結果卻正常顯示了,這個結果十分有趣。

在好奇心的驅使下能夠探查到data事件調用了string_decoder來進行編碼補足的行爲。經過string_decoder對象輸出第一個截取Buffer(事件循xx)時,只返回事件循這個字符串,保留xx。第二次經過string_decoder對象輸出時檢測到上次保留的xx,將上次剩餘內容和本次的Buffer進行從新拼接輸出。因而達到正常輸出的目的。

string_decoder,目前在文件流讀取和網絡流讀取中都有應用到,必定程度上避免了粗魯拼接trunk致使的亂碼錯誤。可是,遺憾在於string_decoder目前只支持utf8編碼。它的思路其實還能夠擴展到其餘編碼上,只是最終是否會支持目前尚不可得知。

鏈接Buffer對象的正確方法

那麼萬能的適應各類編碼並且正確的拼接Buffer對象的方法是什麼呢?咱們從Node.js在github上的源碼中找出這樣一段正確讀取文件,並鏈接buffer對象的方法

var buffers = [];
var nread = 0;
readStream.on('data', function (chunk) {
    buffers.push(chunk);
    nread += chunk.length;
});
readStream.on('end', function () {
    var buffer = null;
    switch(buffers.length) {
        case 0: buffer = new Buffer(0);
            break;
        case 1: buffer = buffers[0];
            break;
        default:
            buffer = new Buffer(nread);
            for (var i = 0, pos = 0, l = buffers.length; i < l; i++) {
                var chunk = buffers[i];
                chunk.copy(buffer, pos);
                pos += chunk.length;
            }
        break;
    }
});

在end事件中經過細膩的鏈接方式,最後拿到理想的Buffer對象。這時候不管是在支持的編碼之間轉換,仍是在不支持的編碼之間轉換(利用iconv模塊轉換),都不會致使亂碼。

簡化鏈接Buffer對象的過程

上述一大段代碼僅只完成了一件事情,就是鏈接多個Buffer對象,而這種場景需求將會在多個地方發生,因此,採用一種更優雅的方式來完成該過程是必要的。筆者基於以上的代碼封裝出一個bufferhelper模塊,用於更簡潔地處理Buffer對象。能夠經過NPM進行安裝:

npm install bufferhelper

下面的例子演示瞭如何調用這個模塊。與傳統data += trunk之間只是bufferHelper.concat(chunk)的差異,既避免了錯誤的出現,又使得代碼能夠獲得簡化而有效地編寫。

var http = require('http');  
var BufferHelper = require('bufferhelper');
http.createServer(function (request, response) {
    var bufferHelper = new BufferHelper();
    request.on("data", function (chunk) {
    bufferHelper.concat(chunk);
    });
    request.on('end', function () {
    var html = bufferHelper.toBuffer().toString();
    response.writeHead(200);
    response.end(html);
    });

}).listen(8001);

因此關於Buffer對象的操做的最佳實踐是:

  • 保持編碼不變,以利於後續編碼轉換
  • 使用封裝方法達到簡潔代碼的目的

參考

關於做者

田永強,新浪微博@樸靈,前端工程師,曾就任於SAP,現就任於淘寶,花名樸靈,致力於NodeJS和Mobile Web App方面的研發工做。雙修先後端JavaScript,寄望將NodeJS引薦給更多的工程師。興趣:讀萬卷書,行萬里路。我的Github地 址:http://github.com/JacksonTian。

相關文章
相關標籤/搜索