理解Bufferr
Buffer结构
Buter是一个像Amray的对象,但它主要用于操作字节。
模块结构
Buffer是JavaScript和c++结合的模块。性能部分是C++, 非性能是JavaScript实现
Bufer所占用的内存不是通过V8分配的。Node在进程启动时就已经加载了它,并将其放在全局对象。在使用Bufer时,无须通过require()即可直接使用。
Buffer对象
Bufer对象类似于数组,它的元素为16进制的两位数,即0到255的数值。
var str = "hello world"
var buf = new Buffer(str, 'utf-8')
console.log(buf)
// <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
不同编码的字符串占用的元素个数各不相同,上面代码中的中文字在UTF-8编码下占用3个元素,字母和半角标点符号占用1个元素。
访问1ength属性得到长度,也可以通过下标访问元素.
var buf = new Buffer(100);
console.log(buf.length)
- 如果给元素的赋值小于0,就将该值逐次加256,知道得到0-255之间的整数。
- 如果得到的数值大于255,就逐次减256,直到得到0~255区间内的数值
- 如果是小数,舍弃小数部分,只保留整数部分。
buf[20] = -100 //156
buf[21] = 300 //44
buf[22] = 3.123 // 3
Buffer内存分配
Bufer对象的内存分配不是在V8的堆内存中,而是在Node的C++层面实现内存的申请的。
为了高效使用申请来的内存,Node使用了slab分配机制。slab是动态内存管理机制。简单而言,slab就是一块申请好的固定大小的内存区域。
- full: 完全分配状态
- partial: 部分分配状态
- empty: 未分配状态。
node以8kb为界限,小于8kb的slab为full,大于8kb的slab为partial。
分配小Buffer对象
如果指定Buffer的大小,小于8kb,会按照小对象的方式进行分配。Buffer的分配过程中˞要使用一个局部变量poo1作为中间处理对象,处于分配状态的slab单元都指向它
s1owBuffer类是在C++中定义的,虽然引用buffer模块可以访问到它,但是不推荐直接操作它,而是用Buffer替代,Buffer对象都是JavaScript层面的,能够被V8的垃圾回收标记回收。但是其内部的parent属性指向的slowBuffer对象却来自于Node自身C++中的定义,是C++层面上的Buffer对象所用内存不在V8的堆中。
var pool;
function allocPool() {
pool = new SlowBuffer(Buffer.poolSize)
pool.used = 0
}
处于empty状态
// 构造小buffer对象
new Buffer(1024)
这次构造会检查pool对象,如果pool对象没有被创建,则会创建一个新的slab单元指向它
// 创建新的slab单元指向它
if(!pool || pool.lenght - pool.used < this.length) allocPool()
记录当前buffer对象的parent属性指向该slab,记录下这个slab的那个位置开始使用的。slab对象自身也记录被使用了多少字节
this.parent = pool
this.offset = pool.used
pool.used += this.length
if (pool.used & 7) pool.used = (pool.used + 8) & ~7
下图为初次分配一个buffer对象的示意图,这种状态的slab状态称为partial
当再次创建buffer对象的时候,构造过程会判断这个slab的剩余空间是否充足,使用剩余空间,更新slab的分配状态。
如果slab剩余的空间不够,将会构造新的slab,原slab中剩余的空间会造成浪费。例如,第次构造1字节的Bufer对象,第二次构造8192字节的Bufer对象,由于第二次分配时slab中的空间不够,所以创建并使用新的slab,第一个slab的8 KB将会被第一个1字节的Bumer对象独占。下面的代码一共使用了两个slab单元:
new Buffer(1)
new Buffer(8192)
同一个slab可能分配给多个Bufer对象使用,只有这些小Bufer对象在作用域释放并都可以回收时,slab的8KB空间才会被回收。
分配大Buffer对象
如果需要超过8KB的Buffer对象,将会直接分配一个slowbuffer对象作为slab单元,这个slab单元会被这个大buffer对象独占。
// Big buffer, just alloc one
this.parent = new SlowBuffer(this.length)
this.offset = 0
小结
真正的内存是在Node的C++层面提供的,JavaScript层面只是使用它。当进行小而频繁的Bufer操作时,采用slab的机制进行预先中请和事后分配,使得JavaScript到操作系统之问不必有过多的内存申请方面的系统调用。对于大块的Bufer而言,则直接使用C++层面提供的内存,而无需细腻的分配操作。
Buffer的转换
字符串转Buffer
构造函数
new Buffer(str, [encoding]);
buf.write(string, [offset], [length], [encoding])
通过构造函数转换的Bufer对象,存储的只能是一种编码类型。encoding参数不传递时,默认按UTF-8编码进行转码和存储。
Buffer转字符串
buf.toString([encoding],[start],[end])
// readable.setEncoding(encoding)
var rs = fs.createReadStream('utf-8', {highWaterMark: 11})
rs.setEncoding('utf-8')
Buffer与性能
通过遇见转换静态内容为buffer对象,可以有效的减少cpu的重复利用。节约服务器资源。
文件读取
fs.createReadStream(path, opts)
{
flags: 'r',
encoding: null,
fd: null,
mode: 0666,
highWaterMark: 64 * 1024
}
可以传递start和end来指定文件的位置范围
{start: 90, end: 99}
fs.createReadStream()的工作方式是在内存中准备一段buffer,然后再fs.read()读取逐步从磁盘讲字节复制到buffer中,完成一次的读取,从这个buffer通过slice方法取出部分数据作为一个小buffer对象。通过data事件传递给调用方。如果buffer用完,则重新分配一个,如果还有剩余,则继续使用。
var pool ;
function allocNewPool(poolSize) {
pool = new Buffer(poolSize)
pool.used = 0
}
highWaterMark的大小对性能有两个影响的点
- highWaterMark的设置对于buffer内存的分配和使用具有一定的影响
- highWaterMark设置过小,可能导致系统调用次数过多。