3. Buffer的转换,终端的乱码的形成。

Buffer对象可以与字符串之间相互转换。目前支持的字符串编码如下:

  • ASCII
  • UTF-8
  • UTF-16LE/UCS-2
  • Base64
  • Binary
  • Hex
1. String与Buffer相互转换

字符串转Buffer主要通from(string, encoding)方法数完成:

var str = 'hello'
var strBuffer = Buffer.from(str) ; //encoding不传默认为utf-8
//<Buffer 68 65 6c 6c 6f>

buffer转字符串 buf.toString(encoding, start, end)

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

encoding: 转换成字符串后的字符编码(默认为utf-8);
start: 转换的起始位置;
end: 转换的起始位置;

这三个参数实现整体货局部的转换。如果Buffer对象由多种编码写入,就需要在局部指定不同的编码,才能转换会正常的编码。

2. Buffer不支持的编码类型

Node的buffer对象支持的编码类型有限,在字符串和Buffer之间转换的类型较少。Buffer提供了一个isEncoding()函数判断编码是否支持转换。

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生态圈的模块来完成(iconv 、iconv-lite)。

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

很多时候cmd或其他终端输出的内容时,经常看到带白色或者黑色背景的问号,那是对无法转换的多字节输出“�”;
如果是单字节,则输出“?”

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当做字符来理解,所以在接受上面的实例时不会觉得有异常。

当输入流中有宽字节编码时,问题就容易显现,在很多用Node开发的网站上看到“����”,就是多字节的转换异常导致的。

data += chunk;

这句代码中隐藏了toString()的操作,等价于:

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

在语境为英文的环境中,toString()不会造成任何问题。但对于宽字节的中文却会形成问题。

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

搭配上面代码的测试数据为李白的《静夜思》。下面是执行之后输出的内容:

床前明��光,疑���地上霜。
举头���明月,低头思��乡。

4. 乱码产生的原因

在使用createReadStream创建文件流使用了highWaterMark,将buffer的长度设为了11,因此文件流需要读取7次才能完成:

读取文件数据: <Buffer e5 ba 8a e5 89 8d e6 98 8e e6 9c>
读取文件数据: <Buffer 88 e5 85 89 ef bc 8c e7 96 91 e6>
读取文件数据: <Buffer 98 af e5 9c b0 e4 b8 8a e9 9c 9c>
读取文件数据: <Buffer e3 80 82 0a e4 b8 be e5 a4 b4 e6>
读取文件数据: <Buffer 9c 9b e6 98 8e e6 9c 88 ef bc 8c>
读取文件数据: <Buffer e4 bd 8e e5 a4 b4 e6 80 9d e6 95>
读取文件数据: <Buffer 85 e4 b9 a1 e3 80 82>

data += chunk;执行了buf.toString()并默认utf-8为编码,中文在utf-8下占用3个字节。第一个buffer对象在输出时,只能显示三个字符(11/3=3余2),Buffer中剩下的2个字节(e6 9c 两个字节无法形成中文字)将会以乱码的形式显示。(注意:中文标点也算三个字节!)

在宽字节的字符转换成buffer的过程中(n个字节代表一个字符,注意n大于1)有截取长度的可能,比如上面使用highWaterMark截取导致,每三个字节代表一个中文字,但是在一个buff中字节的长度不为3n,那些无法解析的字节被抛出

5. setEncoding()与string_decoder()

在上面的示例中,可读流还有一个设置编码的方式setEncoding(),示例如下:

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

readStream.on('data', (chunk) => {
    data += chunk;
    console.log('读取文件数据:', chunk);
});
 
//文件读取完成事件
readStream.on('end', () => {
    console.log(data);
});

结果:

床前明月光,疑是地上霜。
举头望明月,低头思故乡。

setEncoding()方法的作用是让data事件传递的不再是一个buffer对象,而是编码后的字符串。

在这里输出和未调用setEncoding()的输出完全不一样了:

  1. 在调用setEncoding()时,可读流对象在内部设置了一个decoder对象。每次data事件都通过decoder对象进行Buffer到字符串的解码,然后传递给调用者(data事件的回调)。
  2. 所以data接收的不再是buffer对象,而是编码后的字符。
  3. 关键点还是在setEncoding()的内部事件中的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);
=> '床前明' //可以看到只解析了完整的字节编码(前9个)。
var buf2 = Buffer.from([0x88 ,0xe5 ,0x85 ,0x89 ,0xef ,0xbc ,0x8c ,0xe7 ,0x96 ,0x91 ,0xe6]);
 decoder.write(buf2)
=>'月光,疑' //buf1中未解析的两个字节在这得到解析(月字)。
  • 上面的代码中,‘月’字的转码并没有在两个部分分开输出(上面解析的��),
  • StringDecoder在得到编码后,知道宽字节字符串在UTF-8编码下是以3个字节的方式存储的.
  • 在第一次write()时,只输出了9个字节转码形成的字符,‘月’字的前两个字节被保留在StringDecoder的实例内部。
  • 第二次write()时,会将2个剩余的字节和后续11个字节组合,再次用3的倍数字节进行转码。

StringDecoder并非万能的,他目前只能处理UTF-8、base64和UCS-2/UTF-16LE这三种编码。它不能从根本上解决问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容