Buffer在使用中,通常是以一段一段的传输,以下为输入流中读取内容的代码:
var fs = require('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; 这句代码里隐藏了toString()操作,它等价于如下代码:
data = data.toString() + chunk.toString();
对于宽字节的中文,就会有问题。
为了模拟该问题的出现,我们将从文件流中读取Buffer,并且Buffer的长度限制在11,
代码如下:
var rs = fs.createReadStream('test.md', {
highWaterMark: 11});
test.md数据为李白的《静夜思》,执行程序,将会得到以下输出:
床前明???光;疑???地上霜;举头???明月,???头思故乡。
诗歌中出现了多个问号,我们来分析下产生的原因。
由于限定了Buffer的长度为11,从流中读取7次才会完成所有数据的读取,结果如下:
…
之前提到Buffer默认的编码为utf-8,中文字节utf-8编码下占3个字节,所以第一次Buffer读取完后,会显示三个中文,剩余两个字节(e6 9c)会显示乱码(问号)。
第二个Buffer对象的第一个字节也不能形成文字,只能显示乱码。该例子是通过限制Buffer为11个字节导致了问号的出现,如果放宽Buffer字节的长度,该问题仍旧会出现。
在读取流时,可以设置流的编码方式。示例代码如下:
readable.setEncoding(encoding)
改进后的代码如下:
var rs = fs.createReadStream('test.md', {
highWaterMark: 11});
rs.setEncoding('utf8');
我们惊喜地发现,重新执行程序后,输出了正确的中文。
在调用setEncoding()时,可读流对象在内部设置了一个decoder对象。该对象来自string_decoder模块StringDecoder实例对象。
下面用代码具体说明。
var StringDecoder = require('string_decoder').StringDecoder;
var decoder = new StringDecoder('utf8');
var buf1 = new Buffer([0xE5, 0xBA, 0x8A, 0xE5, 0x89, 0x8D, 0xE6, 0x98, 0x8E, 0xE6, 0x9C]);
console.log(decoder.write(buf1));
var buf2 = new Buffer([0x88, 0xE5, 0x85, 0x89, 0xEF, 0xBC, 0x8C, 0xE7, 0x96, 0x91, 0xE6]);
console.log(decoder.write(buf2));
将之前的两个Buffer对象写入decoder中,奇怪的地方在于“月”的转码并没有如平常一样在两个部分分开输出。StringDecoder在得到编码后,知道宽字节字符串在utf-8编码下是以3个字节的方式存储的,所以第一次write()时,只输出9个字节转码形成的字符。第二次write时,将剩余2个字节和11个字节统一进行3个字节转码。
如何才是正确地拼接字符串呢?代码如下:
var chunks = [];
var size = 0;
res.on('data', function (chunk) {
chunks.push(chunk);
size += chunk.length;
});
res.on('end', function () {
var buf = Buffer.concat(chunks, size);
var str = iconv.decode(buf, 'utf8');
console.log(str);
});
正确的拼接方式是用一个数组来存储接收到所有Buffer片段并记录下所有片段的总长度,然后调用Buffer.concat()方法生成一个合并的Buffer对象。Buffer.concat()方法封装了从Buffer对象向大Buffer对象的复制过程。
Buffer.concat = function(list, length) {
if (!Array.isArray(list)) {
throw new Error('Usage: Buffer.concat(list, [length])');
}
if (list.length === 0) {
return new Buffer(0);
} else if (list.length === 1) {
return list[0];
}
if (typeof length !== 'number') {
length = 0;
for (var i = 0; i < list.length; i++) {
var buf = list[i];
length += buf.length;
}
}
var buffer = new Buffer(length);
var pos = 0;
for (var i = 0; i < list.length; i++) {
var buf = list[i];
buf.copy(buffer, pos);
pos += buf.length;
}
return buffer;
};