Buffer是一个Array的对象,主要用于操作字节。
目录
1. 模块结构
Buffer是一个的JavaScript与C++结合的模块。性能相关的部分由C++实现,非性能相关的部分用JavaScript实现。
Buffer所占的内存不是通过V8分配的,属于堆外内存。
Node在进程启动时就家长了Buffer,并将其放置在全局对象上,所以无需require() 直接使用。
2. Buffer对象
其类似数组,元素为16进制的两位数,即0到255的数值
可以通过访问length属性得到长度,同时可以通过下标访问元素
如果元素赋值小于0,就将该值逐次加到256,直到得到一个0到255之间的整数。
如果大于255,就逐次减256,直到得到0到255区间内的值
如果是小数,则舍弃小数部分,只保留整数部分
3. Buffer内存分配
Buffer对象的内存分配不是在V8中的堆内存中,而是在Node的C++层面实现内存的申请的。
Node在内存使用上应用的是在C++层面申请内存、在JavaScript中分配内存的策略。
slab分配机制
一种动态内存管理机制,slab就是一块申请好的固定大小的内存区域,具有三种状态
full:完全分配状态
partial:部分分配状态
empty:没有被分配状态
4. Buffer的转换
Buffer对象可以与字符串之间相互转换:
ASCII、UTF-8、UTF-16LE/UCS-2、Base64、Binary、Hex
(1)字符串转Buffer
通过构造函数完成
new Buffer(str, [encoding]);
一个Buffer对象可以存储不同编码类型的字符串转码的值,可以调用write()方法实现
buf.write(string, [offset], [length], [encoding])
(2)Buffer转字符串
buf.toString([encoding], [start], [end])
可以设置endcoding,start、end三个三参数实现整体或者局部的转换
(3)Buffer不支持的编码类型
由于支持的编码类型有限, 可以使用以下方法判断是否支持转换
Buffer.isEncoding(encoding)
将编码类型输入,就可以知道是否支持转换,对于不支持的编码可以使用 iconv 和 iconv-lite两个模块支持。
iconv-lite 采用纯JS实现,iconv通过C++调用libiconv库实现
前者比后者更加轻量,无须编译和处理环境依赖直接使用。
5. Buffer的拼接
var fs = require('fs');
var rs = fs.createReadSteam('test.md');
var data = '';
rs.on("data", funciton (chunk){
data += chunk;
});
rs.on("end", funciton (){
console.log(data);
})
正常读取字符流的过程,但是在英文环境中是没有问题的,在中文环境中会出现问题。
因为
data += chunk;
实际上执行或者等价的代码是
data = data.toString() + chunk.toString();
所以当我们如果限制每次读取的Buffer长度时,就有可能在读取中文时出现乱码,最简单的解决办法就是提高每一次读取的长度,长度越大出现的概率越低
同时可以通过设置编码的方法
readable.setEncoding(encoding)
此方法在被调用时,可读流对象内部设置了一个decoder对象。
每次data事件都通过该decoder对象进行Buffer到字符串的解码,然后传递给调用者。
通过setEncoding() 的方式不可否认能解决大部分的乱码问题,但并不能从根本上解决该问题。
6. 正确拼接Buffer
淘汰掉setEncoding() 方法后,剩下的解决方案只有将多个小Buffer对象拼接为一个Buffer对象,然后通过iconv-tite一类的模块来转码方式。+=的方式显然不行,正确的Buffer拼接方法应该如下面展示的形式:
var chunks = [];
var size = 0;
res.on('data', function (chunk){
chunks.push(chunk);
size += chunk.length;
});
res.on('end', funciton (){
var buf = Buffer.concat(chunks, size);
var str = iconv.decode(buf, 'utf8');
});
正确的拼接方式是用一个数组来存储接收到的所有Buffer片段并记录下所有片段的总长度,然后调用Buffer.concat() 方法生成一个合成的Buffer对象。Buffer.concat() 方法封装了从小Buffer对象向大Buffer对象的复制过程, 实现过程十分细腻,值得围观学习:
Buffer.concat = funcion(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 linst[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;
}