深入浅出Node.js读书笔记:Buffer的拼接(6.3)

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数据为李白的《静夜思》,执行程序,将会得到以下输出:
床前明???光;疑???地上霜;举头???明月,???头思故乡。

6.3.1 乱码是如何产生的

诗歌中出现了多个问号,我们来分析下产生的原因。

由于限定了Buffer的长度为11,从流中读取7次才会完成所有数据的读取,结果如下:



之前提到Buffer默认的编码为utf-8,中文字节utf-8编码下占3个字节,所以第一次Buffer读取完后,会显示三个中文,剩余两个字节(e6 9c)会显示乱码(问号)。

第二个Buffer对象的第一个字节也不能形成文字,只能显示乱码。该例子是通过限制Buffer为11个字节导致了问号的出现,如果放宽Buffer字节的长度,该问题仍旧会出现。

6.3.2 setEncoding()与string_decoder()

在读取流时,可以设置流的编码方式。示例代码如下:

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个字节转码。

6.3.3 正确拼接Buffer

如何才是正确地拼接字符串呢?代码如下:

	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; 
};

你可能感兴趣的:(Node.js)