3. Buffer的轉(zhuǎn)換,終端的亂碼的形成。

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

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

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

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

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

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

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

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

2. Buffer不支持的編碼類型

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

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生態(tài)圈的模塊來完成(iconv 、iconv-lite)。

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

很多時候cmd或其他終端輸出的內(nèi)容時,經(jīng)常看到帶白色或者黑色背景的問號,那是對無法轉(zhuǎn)換的多字節(jié)輸出“?”;
如果是單字節(jié),則輸出“?”

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當(dāng)做字符來理解,所以在接受上面的實例時不會覺得有異常。

當(dāng)輸入流中有寬字節(jié)編碼時,問題就容易顯現(xiàn),在很多用Node開發(fā)的網(wǎng)站上看到“????”,就是多字節(jié)的轉(zhuǎn)換異常導(dǎo)致的。

data += chunk;

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

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

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

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

搭配上面代碼的測試數(shù)據(jù)為李白的《靜夜思》。下面是執(zhí)行之后輸出的內(nèi)容:

床前明??光,疑???地上霜。
舉頭???明月,低頭思??鄉(xiāng)。

4. 亂碼產(chǎn)生的原因

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

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

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

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

5. setEncoding()與string_decoder()

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

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

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

結(jié)果:

床前明月光,疑是地上霜。
舉頭望明月,低頭思故鄉(xiāng)。

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

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

  1. 在調(diào)用setEncoding()時,可讀流對象在內(nèi)部設(shè)置了一個decoder對象。每次data事件都通過decoder對象進行Buffer到字符串的解碼,然后傳遞給調(diào)用者(data事件的回調(diào))。
  2. 所以data接收的不再是buffer對象,而是編碼后的字符。
  3. 關(guān)鍵點還是在setEncoding()的內(nèi)部事件中的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);
=> '床前明' //可以看到只解析了完整的字節(jié)編碼(前9個)。
var buf2 = Buffer.from([0x88 ,0xe5 ,0x85 ,0x89 ,0xef ,0xbc ,0x8c ,0xe7 ,0x96 ,0x91 ,0xe6]);
 decoder.write(buf2)
=>'月光,疑' //buf1中未解析的兩個字節(jié)在這得到解析(月字)。
  • 上面的代碼中,‘月’字的轉(zhuǎn)碼并沒有在兩個部分分開輸出(上面解析的??),
  • StringDecoder在得到編碼后,知道寬字節(jié)字符串在UTF-8編碼下是以3個字節(jié)的方式存儲的.
  • 在第一次write()時,只輸出了9個字節(jié)轉(zhuǎn)碼形成的字符,‘月’字的前兩個字節(jié)被保留在StringDecoder的實例內(nèi)部。
  • 第二次write()時,會將2個剩余的字節(jié)和后續(xù)11個字節(jié)組合,再次用3的倍數(shù)字節(jié)進行轉(zhuǎn)碼。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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