Node 学习小结 - Buffer生成策略

1,563 阅读3分钟

在node 8.0之前,buffer主要通过构造函数使用new Buffer(...)的方式来创建。因为根据参数的不同,生成buffer的行为和结果都大不相同,所以从node 8.0版本开始,buffer的创建被拆分到几个不同的方法(from, alloc, allocUnsafe)。

在看详细的方法之前,需要知道的是,node为了节约系统资源提高效率,先预申请了一部分内存作为buffer pool。 如果申请的buffer的大小比较小的情况下,会优先从这个pool里面进行分配。 这部分操作,都放到了allocate函数里面:

function allocate(size) {
  if (size <= 0) {
    return new FastBuffer();  // 没size,直接返回一个空buffer
  }
  if (size < (Buffer.poolSize >>> 1)) {   // size小于pool的一半(4kb)
    if (size > (poolSize - poolOffset))  // size比pool剩下的空间大的话,就再申请一个pool
      createPool();
    const b = new FastBuffer(allocPool, poolOffset, size);
    poolOffset += size;
    alignPool();
    return b;
  }
  return createUnsafeBuffer(size);  // 如果size比较大, 则直接申请一个未初始化的buffer
}

Buffer.from

Buffer.from比较复杂,根据入参大致有3种不同的处理方式:

Buffer.from(string, encoding)

当传入的第一个参数为一个字符串时,会调用对应的fromString方法,代码如下:

function fromString(string, encoding) {
  // 确定string在encode后的大小,如果string为0,则返回一个空buffer
  let length;
  if (typeof encoding !== 'string' || encoding.length === 0) {
    if (string.length === 0)
      return new FastBuffer();
    encoding = 'utf8';
    length = byteLengthUtf8(string);
  } else {
    length = byteLength(string, encoding, true);
    if (length === -1)
      throw new ERR_UNKNOWN_ENCODING(encoding);
    if (string.length === 0)
      return new FastBuffer();
  }

  // 如果大小大于poolSize的一半(4kb), 直接分配内存。
  if (length >= (Buffer.poolSize >>> 1))
    return createFromString(string, encoding);

  // 如果大小大于pool里剩下的内存,则新建一个pool
  if (length > (poolSize - poolOffset))
    createPool();
  let b = new FastBuffer(allocPool, poolOffset, length);
  const actual = b.write(string, encoding);
  if (actual !== length) {
    b = new FastBuffer(allocPool, poolOffset, actual);
  }
  poolOffset += actual;
  alignPool();
  return b;
}

c++扩展直接分配内存:

void CreateFromString(const FunctionCallbackInfo<Value>& args) {
  CHECK(args[0]->IsString());
  CHECK(args[1]->IsString());

  enum encoding enc = ParseEncoding(args.GetIsolate(),
                                    args[1].As<String>(),
                                    UTF8);
  Local<Object> buf;

  if (New(args.GetIsolate(), args[0].As<String>(), enc).ToLocal(&buf))
    args.GetReturnValue().Set(buf);
}

总结下来就是3个步骤:

  1. 通过encode的方式,确定最终需要的buffer大小
  2. 如果是个大buffer(>4kb),那就不走buffer pool了,直接通过c++拓展分配内存。
  3. 小buffer的话,看看上一个pool剩下的内存还够不够,够的话继续用,不够了就再新建一个内存来分配。

Buffer.from(arrayBuffer)

function fromArrayBuffer(obj, byteOffset, length) {
  ... byteOffset校对逻辑
  return new FastBuffer(obj, byteOffset, length);
}

如果传入的是一个arrayBuffer, 那就直接生成一个FastBuffer对象,而FastBuffer直接继承了Uint8Array,根据typedArray的定义,如果传入一个ArrayBuffer,那就返回一个新的TypedArray来共享这段内存

When called with a buffer, and optionally a byteOffset and a length argument, a new typed array view is created that views the specified ArrayBuffer. The byteOffset and length parameters specify the memory range that will be exposed by the typed array view. If both are omitted, all of buffer is viewed; if only length is omitted, the remainder of buffer is viewed.

Buffer.from(array/buffer/typedArray)

function fromObject(obj) {
  if (isUint8Array(obj)) {
    const b = allocate(obj.length);   // // 从pool里分配或者直接分配

    if (b.length === 0)
      return b;

    _copy(obj, b, 0, 0, obj.length);  // // 把数据拷贝到新分配的内存上。
    return b;
  }

  if (obj.length !== undefined || isAnyArrayBuffer(obj.buffer)) {
    if (typeof obj.length !== 'number') {
      return new FastBuffer();
    }
    return fromArrayLike(obj);
  }

  if (obj.type === 'Buffer' && Array.isArray(obj.data)) {
    return fromArrayLike(obj.data);
  }
}

// 把类数组的数据(array/buffer)都写入新的buffer
function fromArrayLike(obj) {
  const length = obj.length;
  const b = allocate(length);
  for (var i = 0; i < length; i++)
    b[i] = obj[i];
  return b;
}

Buffer.alloc

Buffer.alloc(size, fill, encoding)接受3个参数,返回一个被初始化了的buffer:

Buffer.alloc = function alloc(size, fill, encoding) {
  assertSize(size);
  if (fill !== undefined && fill !== 0 && size > 0) {
    const buf = createUnsafeBuffer(size);
    return _fill(buf, fill, 0, buf.length, encoding);
  }
  return new FastBuffer(size);
};

// 创建一个未初始化的buffer
function createUnsafeBuffer(size) {
  zeroFill[0] = 0;
  try {
    return new FastBuffer(size);
  } finally {
    zeroFill[0] = 1;
  }
}

通过FastBuffer直接生成一个Uint8Array, 如果传了fill,则还要对生成的Uint8Array做一个填充操作。

Buffer.allocUnsafe

这个就简单了,直接调用allocate函数申请内存

Buffer.allocUnsafe = function allocUnsafe(size) {
  assertSize(size);
  return allocate(size);
};

对应allocUnsafe还有一个allocUnsafeSlow函数,区别就是它直接申请了内存,没有走pool。

Buffer.allocUnsafeSlow = function allocUnsafeSlow(size) {
  assertSize(size);
  return createUnsafeBuffer(size);
};

参考文档

  1. node github仓库
  2. node官方文档