Nodejs - 理解Bufferr

理解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)
  1. 如果给元素的赋值小于0,就将该值逐次加256,知道得到0-255之间的整数。
  2. 如果得到的数值大于255,就逐次减256,直到得到0~255区间内的数值
  3. 如果是小数,舍弃小数部分,只保留整数部分。
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的大小对性能有两个影响的点

  1. highWaterMark的设置对于buffer内存的分配和使用具有一定的影响
  2. highWaterMark设置过小,可能导致系统调用次数过多。
  • 13
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值