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

相关文章
相关标签/搜索