在上篇文章Buffer(Buffer(緩衝器))中,聊了關於編碼的問題。但是編碼有很多小坑,今天我們聊聊坑的問題。 第一個就是BOM頭的問題。 我們都知道,NodeJs是不支援gb2312編碼的, 在此之前得先知道,gb2312編碼中,一個漢字是由兩個位元組(16個位)組成。 在我們寫程式碼的時候經常會遇到一個問題,就是我們寫的程式碼是gbk寫的(gb2312),但NodeJs是不支援的。所以讀取出來的資料,不是我們想要的。
let fs = require('fs');
let path = require('path');
let result = fs.readFileSync(path.join(__dirname,'./1.txt'));//txt的內容是前端開發
console.log(result.toString());
複製程式碼
輸出的內容是亂碼
用編輯器開啟txt檔案也是亂碼 如果不對結果進行toString,得到的buffer的內容是: 通常,我們遇到不支援gbk的檔案,第一反應都會重新設定編碼為utf8格式。例如對txt的操作: 這時,再去獲取result的值let fs = require('fs');
let path = require('path');
let result = fs.readFileSync(path.join(__dirname,'./1.txt'));//txt的編碼已經是utf8
console.log(result);
複製程式碼
結果是
我們都知道uft8格式的檔案,一個漢字3個位元組,此時輸出的結果卻多出3個位元組。因為這是unicode的原因,它會加多3個位元組的字首。這個字首對我們來說是沒有意義的。對result進行toString()轉譯:console.log(result.toString())
複製程式碼
輸出結果:
這時我們就要截掉這個BOM頭。我們看看node原始碼,編譯的時候用了stripBOM的模組,把BOM頭刪掉
// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(internalModule.stripBOM(content), filename);
};
複製程式碼
我們再看看原始碼裡stripBOM的方法
/**
* Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
* because the buffer-to-string conversion in `fs.readFileSync()`
* translates it to FEFF, the UTF-16 BOM.
*/
function stripBOM(content) {
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
return content;
}
複製程式碼
stripBOM拿到內容content以後,取它的第0個,判斷它的第0個是不是0xFEFF,0xFEFF就是那3個字首的字元,那3個字元是不要的,所以做了slice處理。 stripBOM方法裡要求content必須得是字串,因為它截了一個,但是我們的buffer是3個位元組,所以我們要對檔案傳utf8的引數:
let fs = require('fs');
let path = require('path');
let result = fs.readFileSync(path.join(__dirname,'./1.txt'),'utf8');
console.log(result);
複製程式碼
此時result的結果就是一個字串了:
取出result的第一個字元等於0xFEFF的話,就要slice掉。let fs = require('fs');
let path = require('path');
function stripBOM(content) {
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
return content;
}
let result = fs.readFileSync(path.join(__dirname,'./1.txt'),'utf8');
result = stripBOM(result);
console.log(result);
//輸出:前端開發
複製程式碼
一般情況下,我們讀取檔案的時候很少會傳utf8這個引數,如果不傳utf8引數,該怎麼去掉BOM頭?(不傳utf8,得到的就是buffer;傳了utf8,得到的就是字串)
/*
Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
原始碼已經說明,uft8中,EF BB BF表示3個位元組,那麼只需判斷buffer的前3位是EF BB BF,就可以刪掉
*/
function stripBOM(content){
if(Buffer.isBuffer(content)){//判斷是不是buffer
if(content[0]===0xEF&&content[1]===0xBB&&content[2]===0xBF){
return content.slice(3);
}
return content;
}else{ //是string
if(content.charCodeAt(0)===0xFEFF){
return content.slice(1);
}
return content;
}
}
複製程式碼
iconv-lite:讓node支援gb2312
我們用nodejs爬取gb2312網頁的時候,會出現亂碼的情況。可以用iconv-lite
把gbk轉化成utf8,它是第三方模組,所以需要安裝包。這個包的目的就是幫助我們轉化編碼。
如何呼叫:
let iconv = require('iconv-lite');
let fs = require('fs');
let path = require('path');
//iconv.decode(希望解碼的目標,希望按什麼方式解碼)
let result = fs.readFileSync(path.join(__dirname,'./2.txt'));
result = iconv.decode(result,'gbk')
console.log(result.toString())
複製程式碼
所以,如果只想要Buffer,我們一般不傳編碼;如果想看這個結果是個字串,我們就傳utf8
string_decoder
string_decoder
模組用於將Buffer轉成對應的字串。使用者通過呼叫stringDecoder.write(buffer)
,可以獲得buffer對應的字串。
它的特殊之處在於,當傳入的buffer不完整(比如三個位元組的字元,只傳入了兩個),內部會維護一個internal buffer將不完整的位元組cache住,等到使用者再次呼叫stringDecoder.write(buffer)
傳入剩餘的位元組,來拼成完整的字元。
這樣可以有效避免buffer不完整帶來的錯誤,對於很多場景,比如網路請求中的包體解析等,非常有用。
入門例子
這節分別演示了decode.write(buffer)、decode.end([buffer])兩個主要API的用法。
例子一:
decoder.write(buffer)呼叫傳入了Buffer物件,相應的返回了對應的字串你;
const StringDecoder = require('string_decoder').StringDecoder;
const decoder = new StringDecoder('utf8');
// Buffer.from('你') => <Buffer e4 bd a0>
const str = decoder.write(Buffer.from([0xe4, 0xbd, 0xa0]));
console.log(str); // 你
複製程式碼
例子二:
當decoder.end([buffer])被呼叫時,內部剩餘的buffer會被一次性返回。如果此時帶上buffer引數,那麼相當於同時呼叫decoder.write(buffer)和decoder.end()。
const StringDecoder = require('string_decoder').StringDecoder;
const decoder = new StringDecoder('utf8');
// Buffer.from('你好') => <Buffer e4 bd a0 e5 a5 bd>
let str = decoder.write(Buffer.from([0xe4, 0xbd, 0xa0, 0xe5, 0xa5]));
console.log(str); // 你
str = decoder.end(Buffer.from([0xbd]));
console.log(str); // 好
複製程式碼
例子:分多次寫入多個位元組
下面的例子,演示了分多次寫入多個位元組時,string_decoder模組是怎麼處理的。
首先,傳入了,好還差1個位元組,此時,decoder.write(xx)返回你。
然後,再次呼叫decoder.write(Buffer.from([0xbd])),將剩餘的1個位元組傳入,成功返回好。
const StringDecoder = require('string_decoder').StringDecoder;
const decoder = new StringDecoder('utf8');
// Buffer.from('你好') => <Buffer e4 bd a0 e5 a5 bd>
let str = decoder.write(Buffer.from([0xe4, 0xbd, 0xa0, 0xe5, 0xa5]));
console.log(str); // 你
str = decoder.write(Buffer.from([0xbd]));
console.log(str); // 好
複製程式碼
let buffer = Buffer.from('前端開發');
let buff1 = buffer.slice(0,5);
let buff2 = buffer.slice(5);
let {StringDecoder} = require('string_decoder');
let sd = new StringDecoder();
console.log(sd.write(buff1).toString());
console.log(sd.write(buff2).toString());
複製程式碼
例子:decoder.end()時,位元組數不完整的處理
decoder.end(buffer)時,僅傳入了好的第1個位元組,此時呼叫decoder.end(),返回了�,對應的buffer為。
const StringDecoder = require('string_decoder').StringDecoder;
// Buffer.from('好') => <Buffer e5 a5 bd>
let decoder = new StringDecoder('utf8');
let str = decoder.end( Buffer.from([0xe5]) );
console.log(str); // �
console.log(Buffer.from(str)); // <Buffer ef bf bd>
複製程式碼
參考文件: