# Buffer类
Buffer
对象用于表示固定长度的字节序列,在node
中很多api都会涉及到Buffer
,流就是基于Buffer
的
Buffer
类是定型数组Uint8Array
的子类,除了能使用Uint8Array
的方法,还对自身扩展了一些特有方法
之所有Buffer
类是Uint8Array
的子类,在源码中是利用Class
的extends
实现,然而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);
}
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
的原型对象,FastBuffer
在lib/internal/buffer.js
中实现如下,FastBuffer
继承于Uint8Array
// lib/internal/buffer.js
class FastBuffer extends Uint8Array {}
2
我们都知道,Class
的继承其实是有两条继承链,对于上面的extends
继承其实就是如下的关系
FastBuffer.__proto__ === Uint8Array // true
FastBuffer.prototype.__proto__ === Uint8Array.prototype // true
2
这样通过Buffer
构造函数实例化一个buf
对象,不仅可以使用node
对Buffer.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);
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
该方法会在内部先调用assertSize
方法,assertSize
方法内部实现较简单,如果size
不为数值类型或大小不在0
到4GB
字节范围之间都会抛出错误,接着会判断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;
}
}
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;
}
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_byteLength
、TypedArrayFill
、bindingFill
,前两个方法在lib/buffer.js
的最前方定义,bindingFill
方法我还没找到在哪个位置,哈哈哈哈哈哈。。。
const TypedArrayPrototype = ObjectGetPrototypeOf(Uint8ArrayPrototype);
const TypedArrayProto_byteLength = ObjectGetOwnPropertyDescriptor(TypedArrayPrototype, 'byteLength').get;
const TypedArrayFill = TypedArrayPrototype.fill;
2
3
上述代码其实可翻译如下:
const TypedArrayPrototype = Uint8Array.prototype.__proto__
const TypedArrayProto_byteLength = Object.getOwnPropertyDescriptor(Uint8Array.prototype.__proto__, 'byteLength').get
const TypedArrayFill = Uint8Array.prototype.__proto__.fill
2
3
其实到这里就可以看出来,在_fill
内部还是使用的Uint8Array
原型链上的fill
方法进行填充,关于bindingFill
方法,等我晓得了我再补上
# Buffer.allocUnsafe()
// lib/buffer.js
Buffer.allocUnsafe = function allocUnsafe(size) {
assertSize(size);
return allocate(size);
};
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);
}
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;
}
}
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);
};
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);
// ......
};
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);
}
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;
}
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
对象底层引用的内存中,一般默认编码为utf8
,ops.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);
}
}
}
// ......
};
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);
}
}
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);
}
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
对象