今天源码分析一下Nodejs的核心模块Buffer, 官方文档https://nodejs.org/api/buffer.html
在分析源码前,以下问题需要说明:
- Buffer可以看作是原始数据的数组集合,需要特别说明的是,其支持不同形式的编码,而且是在V8 heap之外,可以被GC回收的堆内存。
- 理解大字节序与小字节序的区别,buffer的方法很多区分了大字节序和小序。 注意Buffer的内存是用malloc分配的堆内存,还是从低到高的排列的
分析nodejs 的API和源码后,准备从下面这个方面进行分析:
- Buffer的缓存优化以及内存的分配
- Buffer的构造
- Buffer的类方法
- Buffer的对象属性方法
- Buffer的读与写方法
Buffer的缓存优化和内存分配
源码如下:
// 绑定一个C/C++的模块,用于添加buffer的方法,详情可以查看node_buffer.h node_buffer.cc
var buffer = process.binding('buffer');
// 绑定一个C/C++的模块 用于申请内存,详情可以查看smalloc.h,smalloc.cc
var smalloc = process.binding('smalloc');
// 帮助类
var util = require('util');
// 申请内存的方法,可以简单的理解为C/C++的new malloc
var alloc = smalloc.alloc;
var truncate = smalloc.truncate;
var sliceOnto = smalloc.sliceOnto;
// 可以分配的最长的buffer,值为 0x3fffffff, 也就是1G的大小
var kMaxLength = smalloc.kMaxLength;
// 这个下面用于将C/C++ buffer中的方法,用于Javascript中J。。详情请参考node_buffer.cc
var internal = {};
// 导出模块的类 buffer
exports.Buffer = Buffer;
// 导出模块的类 SlowBuffer
exports.SlowBuffer = SlowBuffer;
exports.INSPECT_MAX_BYTES = 50;
// Buffer缓存的大小为8k
Buffer.poolSize = 8 * 1024;
var poolSize, poolOffset, allocPool;
// 创建缓存库8k的大小缓存
function createPool() {
// 当前的缓存的大小
poolSize = Buffer.poolSize;
// 缓存的内存地址
allocPool = alloc({}, poolSize);
// 可以使用的缓存内存偏移
poolOffset = 0;
}
// 可以理解为系统启动的时候就创建了一个8k大小的buffer缓存
createPool();
从上面的源码可以看出。
- Buffer利用一个8k大小的空间做缓存,在创建一个Buffer的时候可能不会新申请内存,这样可以提高效率。
- Buffer的最大内存为1G
- 系统启动时,已经分配了8k大小的buffer缓存
Buffer的构造
源码如下:
// Buffer的创建最多有两个参数,subject(不同种类型,主要说明buffer的大小与初始值)
// 和encoding(buffer的编码格式)
function Buffer(subject, encoding) {
// 检查,如果不是buffer类型,直接递归调用。生成新Buffer对象
if (!util.isBuffer(this))
return new Buffer(subject, encoding);
// 如果subject参数为数字,表示的Buffer长度
if (util.isNumber(subject)) {
this.length = +subject;
}// 如果 subject参数为字符串的情况
else if (util.isString(subject)) {
//处理第二个参数,说明Buffer默认为 'utf8'编码格式
if (!util.isString(encoding) || encoding.length === 0)
encoding = 'utf8';
// 获取当前Buffer的长度
this.length = Buffer.byteLength(subject, encoding);
// Handle Arrays, Buffers, Uint8Arrays or JSON.
} else if (util.isObject(subject)) {
// 处理是队列或者Buffer JSON格式的情况
if (subject.type === 'Buffer' && util.isArray(subject.data))
subject = subject.data;
//获取的是队列或者buffer的长度
this.length = +subject.length;
} else {
// 如果不是数字,字符串,队列,Buffer等情况,直接抛出异常。
//这说明Buffer的构造只接受subject参数为几种情况
throw new TypeError('must start with number, buffer, array or string');
}
// 检查,如果申请的大小大于1G,直接抛出异常
if (this.length > kMaxLength) {
throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
'size: 0x' + kMaxLength.toString(16) + ' bytes');
}
// 如果为负数,直接清0
if (this.length < 0)
this.length = 0;
else
this.length >>>= 0; // Coerce to uint32.
this.parent = undefined;
// 这里需要特别注意,如果Buffer申请的大小为4k(Buffer.poolSize >>> 1)
if (this.length <= (Buffer.poolSize >>> 1) && this.length > 0) {
// 如果需要的内存已经超过缓存了,直接重新申请
if (this.length > poolSize - poolOffset)
createPool();
//
this.parent = sliceOnto(allocPool,
this,
poolOffset,
poolOffset + this.length);
// 将空闲的内存给新的Buffer
poolOffset += this.length;
// Ensure aligned slices
// 处理字节对齐
if (poolOffset & 0x7) {
poolOffset |= 0x7;
poolOffset++;
}
} else {
// 也就是说,大于4k的情况下,会直接重新分配内存
alloc(this, this.length);
}
// 下面处理的是已经申请的Buffer内存初始化问题
// step1: 如果是数字,无需初始化,直接返回就好。
if (util.isNumber(subject)) {
return;
}
// step1: 如果是字符串,需要用字符串来初始化Buffer空间。
if (util.isString(subject)) {
// In the case of base64 it's possible that the size of the buffer
// allocated was slightly too large. In this case we need to rewrite
// the length to the actual length written.
// 直接将string按照编码格式写入到Buffer内存中,进行初始化。
// 这里需要注意的是base64编码的情况下,可能内存不一样
var len = this.write(subject, encoding);
// Buffer was truncated after decode, realloc internal ExternalArray