node編碼中的坑

在上篇文章Buffer(Buffer(緩衝器))中,聊了關於編碼的問題。可是編碼有不少小坑,今天咱們聊聊坑的問題。 第一個就是BOM頭的問題。 咱們都知道,NodeJs是不支持gb2312編碼的, 在此以前得先知道,gb2312編碼中,一個漢字是由兩個字節(16個位)組成。 在咱們寫代碼的時候常常會遇到一個問題,就是咱們寫的代碼是gbk寫的(gb2312),但NodeJs是不支持的。因此讀取出來的數據,不是咱們想要的。javascript

let fs = require('fs');
let path = require('path');
let result = fs.readFileSync(path.join(__dirname,'./1.txt'));//txt的內容是前端開發
console.log(result.toString());
複製代碼

輸出的內容是亂碼 html

用編輯器打開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())
複製代碼

輸出結果: java

這時咱們就要截掉這個BOM頭。

咱們看看node源碼,編譯的時候用了stripBOM的模塊,把BOM頭刪掉node

// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
  var content = fs.readFileSync(filename, 'utf8');
  module._compile(internalModule.stripBOM(content), filename);
};
複製代碼

咱們再看看源碼裏stripBOM的方法golang

/** * 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的參數:segmentfault

let fs = require('fs');
let path = require('path');
let result = fs.readFileSync(path.join(__dirname,'./1.txt'),'utf8');
console.log(result);
複製代碼

此時result的結果就是一個字符串了: api

取出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>
複製代碼

參考文檔:

string_decoder - 字符串解碼器

你應該記住的一個UTF-8字符「EF BF BD」

巧用string_decoder將buffer轉成string

相關文章
相關標籤/搜索