Buffer

11/13/2022 node

# Buffer类

Buffer对象用于表示固定长度的字节序列,在node中很多api都会涉及到Buffer,流就是基于Buffer

Buffer类是定型数组Uint8Array的子类,除了能使用Uint8Array的方法,还对自身扩展了一些特有方法

之所有Buffer类是Uint8Array的子类,在源码中是利用Classextends实现,然而Buffer的定义使用的却是构造函数形式

// lib/buffer.js
// ......

const {
  FastBuffer,
  // ......
  addBufferPrototypeMethods
} = require('internal/buffer');

FastBuffer.prototype.constructor = Buffer;
Buffer.prototype = FastBuffer.prototype;

// ......

function Buffer(arg, encodingOrOffset, length) {
  showFlaggedDeprecation();
  // Common case.
  if (typeof arg === 'number') {
    if (typeof encodingOrOffset === 'string') {
      throw new ERR_INVALID_ARG_TYPE('string', 'string', arg);
    }
    return Buffer.alloc(arg);
  }
  return Buffer.from(arg, encodingOrOffset, length);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

lib/buffer.js文件中,Buffer.prototype指向FastBuffer的原型对象,FastBufferlib/internal/buffer.js中实现如下,FastBuffer继承于Uint8Array

// lib/internal/buffer.js 
class FastBuffer extends Uint8Array {}
1
2

我们都知道,Class的继承其实是有两条继承链,对于上面的extends继承其实就是如下的关系

FastBuffer.__proto__ === Uint8Array // true
FastBuffer.prototype.__proto__ === Uint8Array.prototype // true
1
2

这样通过Buffer构造函数实例化一个buf对象,不仅可以使用nodeBuffer.prototype上扩展的特有方法,还可以使用继承于Uint8Array上的方法,然而node早在6.0的版本就已经弃用了使用new Buffer的形式实例化buf对象,那么对于Buffer类是Uint8Array的子类又是怎么实现的呢?细心的小伙伴可能发现了在Buffer构造函数内部针对传递的参数如果是数值类型调用的是Buffer.alloc方法,而其它类型调用的是Buffer.from方法

# Buffer.alloc()

该方法定义在lib/buffer.js文件中

// lib/buffer.js
const assertSize = hideStackFrames((size) => {
  if (typeof size !== 'number') {
    throw new ERR_INVALID_ARG_TYPE('size', 'number', size);
  }
  if (!(size >= 0 && size <= kMaxLength)) {
    throw new ERR_INVALID_OPT_VALUE.RangeError('size', size);
  }
});

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);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

该方法会在内部先调用assertSize方法,assertSize方法内部实现较简单,如果size不为数值类型或大小不在04GB字节范围之间都会抛出错误,接着会判断fill参数,如果fill参数不存在,则直接返回new FastBuffer(size)实例化buf对象,由于FastBuffer类继承于定型数组Uint8Array,所以返回的buf对象也能使用Uint8Array原型上的方法。

如果fill不为undefined并且不严格相等于0并且size大于0,则会先调用createUnsafeBuffer方法,该方法在lib/buffer.js文件中被定义

function createUnsafeBuffer(size) {
  zeroFill[0] = 0;
  try {
    return new FastBuffer(size);
  } finally {
    zeroFill[0] = 1;
  }
}
1
2
3
4
5
6
7
8

关于zeroFill可以先忽略,该方法内部其实也是new FastBuffer,返回一个buf对象,接着调用_fill方法,该方法同样在lib/buffer.js文件中被定义

function _fill(buf, value, offset, end, encoding) {
  if (typeof value === 'string') {
    if (offset === undefined || typeof offset === 'string') {
      encoding = offset;
      offset = 0;
      end = buf.length;
    } else if (typeof end === 'string') {
      encoding = end;
      end = buf.length;
    }

    const normalizedEncoding = normalizeEncoding(encoding);
    if (normalizedEncoding === undefined) {
      validateString(encoding, 'encoding');
      throw new ERR_UNKNOWN_ENCODING(encoding);
    }

    if (value.length === 0) {
      // If value === '' default to zero.
      value = 0;
    } else if (value.length === 1) {
      // Fast path: If `value` fits into a single byte, use that numeric value.
      if (normalizedEncoding === 'utf8') {
        const code = value.charCodeAt(0);
        if (code < 128) {
          value = code;
        }
      } else if (normalizedEncoding === 'latin1') {
        value = value.charCodeAt(0);
      }
    }
  } else {
    encoding = undefined;
  }

  if (offset === undefined) {
    offset = 0;
    end = buf.length;
  } else {
    validateInt32(offset, 'offset', 0);
    // Invalid ranges are not set to a default, so can range check early.
    if (end === undefined) {
      end = buf.length;
    } else {
      validateInt32(end, 'end', 0, buf.length);
    }
    if (offset >= end)
      return buf;
  }


  if (typeof value === 'number') {
    // OOB check
    const byteLen = TypedArrayProto_byteLength.call(buf);
    const fillLength = end - offset;
    if (offset > end || fillLength + offset > byteLen)
      throw new ERR_BUFFER_OUT_OF_BOUNDS();

    TypedArrayFill.call(buf, value, offset, end);
  } else {
    const res = bindingFill(buf, value, offset, end, encoding);
    if (res < 0) {
      if (res === -1)
        throw new ERR_INVALID_ARG_VALUE('value', value);
      throw new ERR_BUFFER_OUT_OF_BOUNDS();
    }
  }

  return buf;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

该方法逻辑较清晰,一共有5个参数,后面的3个参数offset、end、encoding都是可选参数,如果对buf填充的数据为字符串, 则首先会对3个可选参数进行赋值处理,encoding默认为utf8,如果value为空字符串,则对buf对象底层引用的arrayBuffer对象的每个二进位填充为0,如果value是长度为1的字符串,对不同的编码填充的值也有所区别,该方法大部分的逻辑都是在处理三个默认参数的值,其实该方法内部最重要的三部分是TypedArrayProto_byteLengthTypedArrayFillbindingFill,前两个方法在lib/buffer.js的最前方定义,bindingFill方法我还没找到在哪个位置,哈哈哈哈哈哈。。。

const TypedArrayPrototype = ObjectGetPrototypeOf(Uint8ArrayPrototype);
const TypedArrayProto_byteLength = ObjectGetOwnPropertyDescriptor(TypedArrayPrototype, 'byteLength').get;
const TypedArrayFill = TypedArrayPrototype.fill;
1
2
3

上述代码其实可翻译如下:

const TypedArrayPrototype = Uint8Array.prototype.__proto__
const TypedArrayProto_byteLength = Object.getOwnPropertyDescriptor(Uint8Array.prototype.__proto__, 'byteLength').get
const TypedArrayFill = Uint8Array.prototype.__proto__.fill
1
2
3

其实到这里就可以看出来,在_fill内部还是使用的Uint8Array原型链上的fill方法进行填充,关于bindingFill方法,等我晓得了我再补上

# Buffer.allocUnsafe()

// lib/buffer.js
Buffer.allocUnsafe = function allocUnsafe(size) {
  assertSize(size);
  return allocate(size);
};
1
2
3
4
5

Buffer.allocUnsafe会直接调用allocate方法

// lib/buffer.js
function allocate(size) {
  if (size <= 0) {
    return new FastBuffer();
  }
  if (size < (Buffer.poolSize >>> 1)) {
    if (size > (poolSize - poolOffset))
      createPool();
    const b = new FastBuffer(allocPool, poolOffset, size);
    poolOffset += size;
    alignPool();
    return b;
  }
  return createUnsafeBuffer(size);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

allocate方法内部会先对size参数做判断,如果size小于等于0,则直接返回new FastBuffer生成的实例对象,如果size小于内部内存池大小的一半,并且size超出内部内存池剩余的空间,则会创建一个新的内存池,返回的buf对象会将内部内存池作为底层引用的ArrayBuffer对象,如果size大于内部内存池大小的一半,则接着调用createUnsafeBuffer方法

// lib/buffer.js
function createUnsafeBuffer(size) {
  zeroFill[0] = 0;
  try {
    return new FastBuffer(size);
  } finally {
    zeroFill[0] = 1;
  }
}
1
2
3
4
5
6
7
8
9

该方法在前面也分析过,不过按照node文档分析,该方法返回的buf对象的底层内存每个字节都没有被初始化,如需用到底层内存每个字节都初始化为0的buf对象可直接使用Buffer.alloc()方法或者Buffer.allocUnsafe(size).fill(0)方法

# Buffer.allocUnsafeSlow()

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

1
2
3
4
5

Buffer.allocUnsafeSlow()方法直接调用createUnsafeBuffer方法,返回的buf对象的底层内存的每个字节都未用0初始化

# Buffer.alloc、Buffer.allocUnsafe()、Buffer.allocUnsafeSlow()的区别

  • Buffer.alloc(size[, fill[, encoding]])方法创建的buf对象底层内存每个字节都会用0进行初始化
  • Buffer.allocUnsafe(size)方法当size参数小于内部内存池大小的一半则会将内部内存池作为底层内存,返回的buf对象的底层内存的每个字节都未用0初始化
  • Buffer.allocUnsafeSlow(size)方法返回的buf对象未用内部内存池作为底层内存,但是底层内存的每个字节都未用0初始化

# Buffer.from()

# 字符串参数

lib/buffer.js文件中该方法被定义

// lib/buffer.js
Buffer.from = function from(value, encodingOrOffset, length) {
  if (typeof value === 'string')
    return fromString(value, encodingOrOffset);

  // ......

};
1
2
3
4
5
6
7
8

首先先分析参数为字符串的情况,会直接调用fromString方法

// lib/buffer.js
function fromString(string, encoding) {
  let ops;
  if (typeof encoding !== 'string' || encoding.length === 0) {
    if (string.length === 0)
      return new FastBuffer();
    ops = encodingOps.utf8;
    encoding = undefined;
  } else {
    ops = getEncodingOps(encoding);
    if (ops === undefined)
      throw new ERR_UNKNOWN_ENCODING(encoding);
    if (string.length === 0)
      return new FastBuffer();
  }
  return fromStringFast(string, ops);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

该方法内部存在两种情况,指定编码或未指定编码(编码默认为utf8),在这两种情况下,都会获取编码选项对象ops,如果string为空字符串则直接返回new FastBuffer()生成的实例对象,在未指定编码的情况下encoding会赋为undefined,最后会接着调用fromStringFast方法

// lib/buffer.js
function fromStringFast(string, ops) {
  const length = ops.byteLength(string);

  if (length >= (Buffer.poolSize >>> 1))
    return createFromString(string, ops.encodingVal);

  if (length > (poolSize - poolOffset))
    createPool();
  let b = new FastBuffer(allocPool, poolOffset, length);
  const actual = ops.write(b, string, 0, length);
  if (actual !== length) {
    // byteLength() may overestimate. That's a rare case, though.
    b = new FastBuffer(allocPool, poolOffset, actual);
  }
  poolOffset += actual;
  alignPool();
  return b;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

该方法首先会根据编码获取string参数的字节长度,当字节长度length大于内部内存池的一半,则会调用createFromString方法,该方法为node内部方法,还没找到地儿,先略过,如果字节长度length大于内部内存池poolOffset到字节尾部的长度,则调用createPool方法重新创建一个内存池,allocPool为长度默认为8 * 1024字节的ArrayBuffer内存池,该方法返回的buf对象底层引用的是内存池中指定的偏移量和字节长度的ArrayBuffer对象,然后根据编码选项对象的ops.write方法将string数据写入到buf对象底层引用的内存中,一般默认编码为utf8ops.write方法为内部绑定的utf8Write方法,最后递增内存池的偏移量poolOffset和调用alignPool方法

# 数组/类数组对象/buffer对象

// lib/buffer.js
Buffer.from = function from(value, encodingOrOffset, length) {

  // ......

  if (typeof value === 'object' && value !== null) {
    if (isAnyArrayBuffer(value))
      return fromArrayBuffer(value, encodingOrOffset, length);

    const valueOf = value.valueOf && value.valueOf();
    if (valueOf != null &&
        valueOf !== value &&
        (typeof valueOf === 'string' || typeof valueOf === 'object')) {
      return from(valueOf, encodingOrOffset, length);
    }

    const b = fromObject(value);
    if (b)
      return b;

    if (typeof value[SymbolToPrimitive] === 'function') {
      const primitive = value[SymbolToPrimitive]('string');
      if (typeof primitive === 'string') {
        return fromString(primitive, encodingOrOffset);
      }
    }
  }

  // ......
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

value为一个数组/类数组对象/buffer对象,会直接调用fromObject方法

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

  if (obj.type === 'Buffer' && ArrayIsArray(obj.data)) {
    return fromArrayLike(obj.data);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

fromObject方法内对参数obj.length属性不为数值类型时返回new FastBuffer生成的实例对象,否则会直接调用fromArrayLike方法

// lib/buffer.js
function fromArrayLike(obj) {
  if (obj.length <= 0)
    return new FastBuffer();
  if (obj.length < (Buffer.poolSize >>> 1)) {
    if (obj.length > (poolSize - poolOffset))
      createPool();
    const b = new FastBuffer(allocPool, poolOffset, obj.length);
    b.set(obj, 0);
    poolOffset += obj.length;
    alignPool();
    return b;
  }
  return new FastBuffer(obj);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

fromArrayLike方法内部,会首先对length做判断,当小于等于0会直接返回new FastBuffer生成的实例对象,当obj.length小于内部内存池大小的一半,则会将内存池作为返回buf对象底层内存,当obj.length小于内部内存池剩余的空间,则会创建一个新内部内存池,接着将obj的数据利用TypedArray.prototype.set方法保存在返回的buf对象中,当obj.length大于内部内存池大小的一半,则会直接返回new FastBuffer(obj)生成的buf对象

最后更新时间: 12/4/2022, 1:44:46 PM