3. Buffer的轉換,終端的亂碼的形成。

Buffer對象可以與字符串之間相互轉換。目前支持的字符串編碼如下:

  • ASCII
  • UTF-8
  • UTF-16LE/UCS-2
  • Base64
  • Binary
  • Hex
1. String與Buffer相互轉換

字符串轉Buffer主要通from(string, encoding)方法數完成:

var str = 'hello'
var strBuffer = Buffer.from(str) ; //encoding不傳默認為utf-8
//<Buffer 68 65 6c 6c 6f>

buffer轉字符串 buf.toString(encoding, start, end)

strBuffer.toString()
//'hello'
 strBuffer.toString(undefined,1,2)
//'e'

encoding: 轉換成字符串后的字符編碼(默認為utf-8);
start: 轉換的起始位置;
end: 轉換的起始位置;

這三個參數實現整體貨局部的轉換。如果Buffer對象由多種編碼寫入,就需要在局部指定不同的編碼,才能轉換會正常的編碼。

2. Buffer不支持的編碼類型

Node的buffer對象支持的編碼類型有限,在字符串和Buffer之間轉換的類型較少。Buffer提供了一個isEncoding()函數判斷編碼是否支持轉換。

Buffer.isEncoding(encoding)
Buffer.isEncoding('utf-8') //true

在中國常用的GBK、GB2312、BIG-5編碼都不在支持的行列中。

Buffer.isEncoding('GBK') //false
Buffer.isEncoding('GB2312') //false
Buffer.isEncoding('BIG-5') //false

對于不支持的編碼類型,可以借助Node生態圈的模塊來完成(iconv 、iconv-lite)。

var iconv = require('iconv-lite');
var str  = iconv.decode(buf, 'GBK');
var buf = iconv.encode('簡單字符', 'GBK');

很多時候cmd或其他終端輸出的內容時,經常看到帶白色或者黑色背景的問號,那是對無法轉換的多字節輸出“?”;
如果是單字節,則輸出“?”

3. Buffer的拼接

Buffer在使用場景中,通常是一段段的方式傳輸:

var fs = reuire('fs');
var rs = fs.createReadStream('test.md');
var data = '';
rs.on('data',function(chunk){
  data += chunk
});
rs.on("end",function(){
  console.log(data)
})

上面的代碼用于流讀取的示范,data時間中獲取的chunk對象就是Buffer對象。但是很容易將Buffer當做字符來理解,所以在接受上面的實例時不會覺得有異常。

當輸入流中有寬字節編碼時,問題就容易顯現,在很多用Node開發的網站上看到“????”,就是多字節的轉換異常導致的。

data += chunk;

這句代碼中隱藏了toString()的操作,等價于:

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

在語境為英文的環境中,toString()不會造成任何問題。但對于寬字節的中文卻會形成問題。

var fs = require('fs');
var readStream = fs.createReadStream('test.md',{highWaterMark:11});
var data = '';
//文件讀取中事件·····
readStream.on('data', (chunk) => {
    data += chunk;
    console.log('讀取文件數據:', chunk);
});
 
//文件讀取完成事件
readStream.on('end', () => {
    console.log(data);
});

搭配上面代碼的測試數據為李白的《靜夜思》。下面是執行之后輸出的內容:

床前明??光,疑???地上霜。
舉頭???明月,低頭思??鄉。

4. 亂碼產生的原因

在使用createReadStream創建文件流使用了highWaterMark,將buffer的長度設為了11,因此文件流需要讀取7次才能完成:

讀取文件數據: <Buffer e5 ba 8a e5 89 8d e6 98 8e e6 9c>
讀取文件數據: <Buffer 88 e5 85 89 ef bc 8c e7 96 91 e6>
讀取文件數據: <Buffer 98 af e5 9c b0 e4 b8 8a e9 9c 9c>
讀取文件數據: <Buffer e3 80 82 0a e4 b8 be e5 a4 b4 e6>
讀取文件數據: <Buffer 9c 9b e6 98 8e e6 9c 88 ef bc 8c>
讀取文件數據: <Buffer e4 bd 8e e5 a4 b4 e6 80 9d e6 95>
讀取文件數據: <Buffer 85 e4 b9 a1 e3 80 82>

data += chunk;執行了buf.toString()并默認utf-8為編碼,中文在utf-8下占用3個字節。第一個buffer對象在輸出時,只能顯示三個字符(11/3=3余2),Buffer中剩下的2個字節(e6 9c 兩個字節無法形成中文字)將會以亂碼的形式顯示。(注意:中文標點也算三個字節!)

在寬字節的字符轉換成buffer的過程中(n個字節代表一個字符,注意n大于1)有截取長度的可能,比如上面使用highWaterMark截取導致,每三個字節代表一個中文字,但是在一個buff中字節的長度不為3n,那些無法解析的字節被拋出?

5. setEncoding()與string_decoder()

在上面的示例中,可讀流還有一個設置編碼的方式setEncoding(),示例如下:

var rs = fs.craateReadStream('test.md', {highWaterMark:11});
 rs.setEncoding('utf-8');//setEncoding

readStream.on('data', (chunk) => {
    data += chunk;
    console.log('讀取文件數據:', chunk);
});
 
//文件讀取完成事件
readStream.on('end', () => {
    console.log(data);
});

結果:

床前明月光,疑是地上霜。
舉頭望明月,低頭思故鄉。

setEncoding()方法的作用是讓data事件傳遞的不再是一個buffer對象,而是編碼后的字符串。

在這里輸出和未調用setEncoding()的輸出完全不一樣了:

  1. 在調用setEncoding()時,可讀流對象在內部設置了一個decoder對象。每次data事件都通過decoder對象進行Buffer到字符串的解碼,然后傳遞給調用者(data事件的回調)。
  2. 所以data接收的不再是buffer對象,而是編碼后的字符。
  3. 關鍵點還是在setEncoding()的內部事件中的decoder對象。

decoder對象來自于string_decoder模塊StringDecoder的實例對象。看下面的代碼:

var StringDecoder = resquier('string_decoder').StringDecoder;
var decoder = new StringDecoder();
var buf1 = Buffer.from([0xe5,0xba,0x8a,0xe5,0x89,0x8d,0xe6,0x98 ,0x8e ,0xe6 ,0x9c]);
decoder.write(buf1);
=> '床前明' //可以看到只解析了完整的字節編碼(前9個)。
var buf2 = Buffer.from([0x88 ,0xe5 ,0x85 ,0x89 ,0xef ,0xbc ,0x8c ,0xe7 ,0x96 ,0x91 ,0xe6]);
 decoder.write(buf2)
=>'月光,疑' //buf1中未解析的兩個字節在這得到解析(月字)。
  • 上面的代碼中,‘月’字的轉碼并沒有在兩個部分分開輸出(上面解析的??),
  • StringDecoder在得到編碼后,知道寬字節字符串在UTF-8編碼下是以3個字節的方式存儲的.
  • 在第一次write()時,只輸出了9個字節轉碼形成的字符,‘月’字的前兩個字節被保留在StringDecoder的實例內部。
  • 第二次write()時,會將2個剩余的字節和后續11個字節組合,再次用3的倍數字節進行轉碼。

StringDecoder并非萬能的,他目前只能處理UTF-8、base64和UCS-2/UTF-16LE這三種編碼。它不能從根本上解決問題。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容