在Node中,应用需要处理网络协议、操作数据库、处理图片、接收上传文件等,在网络流和文件的操作中,还需要处理大量二进制数据。于是Buffer对象应运而生了。
Buffer结构
Buffer是一个像Array的对象,但是主要用来操作字节。
模块结构
Buffer是一个典型的JavaScript和C++结合的模块,它将性能相关部分用C++实现,将非性能部分用JS实现。
由于Buffer太过常见了,Node在进程启动时就已经加载了它,并将其放在全局对象上,所以在使用Buffer的时候,无须通过require就可以直接使用。
Buffer对象
Buffer对象类似于数组,它的元素为16进制的两位数,即0-255。
中文字在UTF-8编码下占用3个元素,字母和半角字符占用一个元素。
可以访问Buffer对象的length属性得到长度,也可以通过下标访问元素。
构造对象:
var buf = new Buffer(100);
console.log(buf.length); // => 100
这些都和Array很像对吧~
上述代码分配了一个100字节的Buffer对象,它的元素值是一个0-255的随机值。
但是如果我们给Buffer[n]分配的不是0-255的整数或者是小数的时候回怎样呢?
如果给元素的赋值小于0,就会逐次+256,直到得到一个0-255的整数。如果大于255就逐次-256。如果是小数,就直接舍弃小数部分。(喵)
Buffer内存分配
Buffer对象的内存分配不是在V8的堆内存中,而是在Node的C++层面实现内存申请的。
也就是,Node在内存上应用的是在C++层面申请内存,在JS中分配内存的策略。
为了高效的使用申请来的内存,Node采用了slab分配机制。slab是一种动态内存管理机制。
简单而言,slab就是一块申请好的固定大小的内存区域,它有如下3种状态。
1、full:完全分配
2、partial:部分分配
3、empty:没有被分配
Node以8KB为界限来区分Buffer是大对象还是小对象。这个8KB的值也就是一个slab的大小,在JS层面,以它为单位单元来进行内存的分配。
分配小Buffer对象
使用局部变量pool作为中间处理对象。
利用pool的used属性记录使用了slab多少个字节。
再次创建Buffer的时候,回判断当前slab的剩余空间是否足够,不够的话就会构造新的slab,那么原来slab中剩余的空间将被浪费。
分配大Buffer对象
如果需要超过8KB的Buffer对象,将回直接分配一个SlowBuffer对象作为slab单元,这个slab单元将会被这个大Buffer对象独占。
总结:真正的内存是在Node的C++层面提供的,JS层面只是使用它。
Buffer的转换
支持的编码类型:
ASCII
UTF-8
UTF-16LE/UCS-2
Base64
Binary
Hex
字符串转Buffer
通过构造函数完成:
new Buffer(str, [encoding]);// encoding默认为UTF-8
一个Buffer对象可以存储不同编码类型的字符串转码的值。
buf.write(string, [offset], [length], [encoding])
Buffer转字符串
buf.toString([encoding], [start], [end])
Buffer不支持的编码类型
检测是否支持转换某个编码格式:
Buffer.isEncoding(encoding)
很遗憾,在中国常用的GBK,GB2312和BIG-5编码都不支持。(啊哦)
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);
});
这里的chunk对象就是Buffer对象。
但是,一旦输入流中有宽字节编码时,问题就会暴露出来——会出现乱码。
潜藏的问题来源于:data += chunk;
这段话等价于:data = data.toString() + chunk.toString();
对于宽字节的中文,就会出现问题
乱码是如何产生的
原因就是,中文字在UTF-8编码下占用3个字节,当一个中文字正好被Buffer对象截断的时候,就会出现乱码。
setEncoding & string_decoder
readable.setEncoding(encoding)
var rs = fs.createReadStream('test.md', { highWaterMark: 11});
rs.setEncoding('utf8');
这样就可以消除乱码。
远离就是通过内部的decoder对象,将中文字的3个字符凑在一起。
它并不是万能的,目前只能处理UTF-8、Base64、UCS-2/UTF-16LE这3种编码。
正确拼接Buffer
这才是正确拼接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.concat源码:
Buffer与性能
在应用中,我们通常会操作字符串,但是数据一旦在网络中传输,都需要转化为Buffer。
通过预先转换静态内容为Buffer对象,可以有效的减少CPU的重复使用,节省服务器资源。在Node构建的Web应用中,可以选择将页面中的动态内容和静态内容分离,静态内容部分可以通过预先转换为Buffer的方式,使性能得到提升。