前端之家收集整理的这篇文章主要介绍了
深入理解Node中的buffer模块,
前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
在Node、ES2015出现之前,前端工程师只需要进行一些简单的字符串或DOM操作就可以满足业务需要,所以对二进制数据是比较陌生。node出现以后,前端面对的技术场景发生了变化,可以深入到网络传输、文件操作、图片处理等领域,而这些操作都与二进制数据紧密相关。
Node里面的buffer,是一个二进制数据容器,数据结构类似与数组,数组里面的方法在buffer都存在(slice操作的结果不一样)。下面就从源码(v6.0版本)层面分析,揭开buffer操作的面纱。
1. buffer的基本使用
在Node 6.0以前,直接使用new Buffer,但是这种方式存在两个问题:
- 参数复杂: 内存分配,还是内存分配+内容写入,需要根据参数来确定
- 安全隐患: 分配到的内存可能还存储着旧数据,这样就存在安全隐患
// 不小心,旧数据就被读取出来了
buf1.toString() // '�\tpk�\u0000\u0000P:'
为了解决上述问题,Buffer提供了Buffer.from
、Buffer.alloc
、Buffer.allocUnsafe
、Buffer.allocUnsafeSlow
四个方法来申请内存。
// 默认情况下,用0进行填充
buf2.toString() //'\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
// 上述操作就相当于
const buf1 = new Buffer(10);
buf.fill(0);
buf.toString(); // '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000'
2. buffer的结构
buffer是一个典型的javascript与c++结合的模块,其性能部分用c++实现,非性能部分用javascript来实现。

下面看看buffer模块的内部结构:
buffer模块提供了4个接口:
- Buffer: 二进制数据容器类,node启动时默认加载
- SlowBuffer: 同样也是二进制数据容器类,不过直接进行内存申请
- INSPECT_MAX_BYTES: 限制bufObject.inspect()输出的长度
- kMaxLength: 一次性内存分配的上限,大小为(2^31 - 1)
其中,由于Buffer经常使用,所以node在启动的时候,就已经加载了Buffer,而其他三个,仍然需要使用require('buffer').***。
关于buffer的内存申请、填充、修改等涉及性能问题的操作,均通过c++里面的node_buffer.cc来实现:
3. 内存分配的策略
Node中Buffer内存分配太过常见,从系统性能考虑出发,Buffer采用了如下的管理策略。

3.1 Buffer.from
Buffer.from(value,...)
用于申请内存,并将内容写入刚刚申请的内存中,value值是多样的,Buffer是如何处理的呢?让我们一起看看源码:
if (value instanceof ArrayBuffer)
return fromArrayBuffer(value,length);
if (typeof value === 'string')
return fromString(value,encodingOrOffset);
return fromObject(value);
};
value可以分成三类:
- ArrayBuffer的实例: ArrayBuffer是ES2015里面引入的,用于在浏览器端直接操作二进制数据,这样Node就与ES2015关联起来,同时,新创建的Buffer与ArrayBuffer内存是共享的
- string: 该方法实现了将字符串转变为Buffer
- Buffer/TypeArray/Array: 会进行值的copy
3.1.1 ArrayBuffer的实例
Node v6与时俱进,将浏览器、node中对二进制数据的操作关联起来,同时二者会进行内存的共享。
v1[0] = 12
console.log('second,v1) // second,typeArray: Uint8Array [ 12,0 ]
console.log('second,buf) // second,Buffer:
在上述操作中,对ArrayBuffer的操作,引起Buffer值的修改,说明二者在内存上是同享的,再从源码层面了解下这个过程:
>>= 0;
if (typeof length === 'undefined')
return binding.createFromArrayBuffer(obj,byteOffset);
length >>>= 0;
return binding.createFromArrayBuffer(obj,length);
}
// c++ 模块中的node_buffer:
void CreateFromArrayBuffer(const FunctionCallbackInfo& args) {
...
Local ab = args[0].As();
...
Local ui = Uint8Array::New(ab,offset,max_length);
...
args.GetReturnValue().Set(ui);
}
3.1.2 string
可以实现字符串与Buffer之间的转换,同时考虑到操作的性能,采用了一些优化策略避免频繁进行内存分配:
= (Buffer.poolSize >>> 1))
return binding.createFromString(string,encoding);
// 当字符所需字节数小于4KB: 借助allocPool先申请、后分配的策略
if (length > (poolSize - poolOffset))
createPool();
var actual = allocPool.write(string,poolOffset,encoding);
var b = allocPool.slice(poolOffset,poolOffset + actual);
poolOffset += actual;
alignPool();
return b;
}
a. 直接内存分配
当字符串所需要的字节大于4KB时,如何还从8KB的buffer pool中进行申请,那么就可能存在内存浪费,例如:
poolSize - poolOffset < 4KB: 这样就要重新申请一个8KB的pool,刚才那个pool剩余空间就会被浪费掉
看看c++是如何进行内存分配的:
& args) {
...
Local