在上篇文章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>
参考文档: