Node -- 理解Buffer

来源:互联网 发布:微商推广软件 编辑:程序博客网 时间:2024/06/05 10:46

在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的转换

支持的编码类型:

ASCIIUTF-8UTF-16LE/UCS-2Base64BinaryHex

字符串转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的方式,使性能得到提升。

原创粉丝点击