日常的学习笔记,包括 ES6、Promise、Node.js、Webpack、http 原理、Vue全家桶,后续可能还会继续更新 Typescript、Vue3 和 常见的面试题 等等。
Buffer
参考文献 buffer 缓冲区
缓冲区 Buffer
是暂时存放输入输出数据的一段内存。JS没有二进制数据类型,而在处理TCP和文件流的时候,必须要处理二进制数据。所以 Node
提供了一个 Buffer
对象 来提供对二进制数据的操作。
Buffer
表示固定内存分配的全局对象,也就是说要放到缓存区中的字节数需要提前确定。而 Buffer
好比由一个 8位字节 组成的数组,可以有效的在JavasScript中表示二进制数据。
Buffer
简单来说就是 node
中的 16进制 ,但 Buffer
在内存的标识也会全部使用 2进制 来进行表示。
(注:目前以无法使用 new Buffer()
创建 Buffer 实例,会存在安全性等问题。已被禁止使用。)
Buffer.alloc
Buffer
代表的是内存,一旦声明好,就不能进行更改。
如果想要更改 Buffer
的大小,改小则对内存进行 截取 。 改大的话就需要创建一个更大的内存空间,将数据拷贝进行,也就是我们俗称的 扩容 。
这时候就可以用到 Buffer
类的内置方法, Buffer.alloc()
。
Buffer.alloc(size[, fill[, encoding]])
,表示分配 size
个字节的新 Buffer
。 如果 fill
为 undefined
,则 Buffer
将以零填充。
- size:新的
Buffer
所需的长度。 - fill:用于预填充新
Buffer
的值,默认值为0
。 - encoding:如果
fill
是字符串,则这就是它的编码。默认值为utf8
。
// 创建了一个指定长度的buffer实例
let buf1 = Buffer.alloc(3); // 最小单位是 3字节
console.log(buf1); // <Buffer 00 00 00>
let buf2 = Buffer.alloc(6); // 单位是 6
console.log(buf2); // <Buffer 00 00 00 00 00 00>
Buffer.from
上一篇文章中,我们曾经使用 Buffer.from
来创建过 base64 。
Buffer.from
方法用于创建包含指定 字符串,数组 或 buffer 的新 Buffer
实例。
Buffer.from
可以传入的参数有很多,这里我们只扩展 字符串 和 数组 两种。
Buffer.from(array)
Buffer.from(array)
使用 0
– 255
范围内的字节 array
分配新的 Buffer
。 该范围之外的数组条目将被截断以符合它。
let buf1 = Buffer.from([0xe8, 0x8e, 0xab])
console.log(buf1); // <Buffer e8 8e ab>
let buf2 = Buffer.from([256, 0x8e, 0xab]) // 超过长度会自动取余
console.log(buf2); // <Buffer 00 8e ab>
let buf2 = Buffer.from(['aaa', 0x8e, 0xab]) // 不能在数组内存放其他数据类型
console.log(buf2); // <Buffer 00 8e ab>
(注:很少使用这种方法来定义 buffer
,因为需要指定存放的内容)
Buffer.from(string)
Buffer.from(string[, encoding])
创建包含 string
的新 Buffer
。 encoding
参数标识将 string
转换为字节时要使用的字符编码。
- string: 要编码的字符串。
- encoding:
string
的编码,默认值为utf8
。
let buf = Buffer.from('莫小尚');
console.log(buf); // <Buffer e8 8e ab e5 b0 8f e5 b0 9a>
Buffer.from(string)
是目前 Buffer
经常使用的方法。这个方法可以存储数据,存储的数据可以用 Buffer
进行表示。同时也可以和字符串之间进行相互转化。
// 使用 .toString() 方法,将buffer转换成字符串
console.log(buf.toString()); // 莫小尚
// 可以转换成任意指定编码
console.log(buf.toString('base64')); // 6I6r5bCP5bCa
buffer.toString()
默认值为 utf8
。
我们在进行读写操作时,如果不指定编码,则所有读取的文件内容都是 buffer
类型。
// test.txt
123456789
// index.js
const fs = require('fs');
let r = fs.readFileSync('./test.txt'); // 不指定 utf-8 编码格式
console.log(r); // <Buffer 31 32 33 34 35 36 37 38 39>
Buffer的扩容
我们在操作 Buffer
时,会遇到原本规定的内存大小不够的情况,这样我们就需要对 Buffer
进行扩容。
const buf1 = Buffer.from('莫')
const buf2 = Buffer.from('小尚')
const bigBuf = Buffer.alloc(buf1.length + buf2.length);
console.log(bigBuf); // <Buffer 00 00 00 00 00 00 00 00 00>
buf.length
返回 buf
中的字节数。
这样我们就创建了一个更大的 Buffer
对象,现在我们需要将内容拷贝到这个大 buffer 中。
buf1.copy(bigBuf, 0, 0, buf1.length);
buf2.copy(bigBuf, buf1.length, 0, buf2.length);
console.log(bigBuf.toString()); // 莫小尚
这里我们使用到了 buf.copy()
方法,稍后会进行讲解。
这样我们就完成了一个简单的扩容操作。 (注:在实际工作中,此方法并不常用。我们一般会使用 buf.concat
来进行扩容操作。)
const buf1 = Buffer.from('莫');
const buf2 = Buffer.from('小尚');
const bigBuf = Buffer.concat([buf1, buf2]);
console.log(bigBuf.toString()); // 莫小尚
关于 buf.concat
的使用,稍后也会进行详解。
Buffer.copy
buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])
将数据从 buf
的区域复制到 target
的区域,即使 target
内存区域与 buf
重叠。
- target:被拷贝的
Buffer
或Uint8Array
,也就是我们的 大容量Buffer
。 - targetStart:
target
内开始写入的偏移量,默认值为0
。 - sourceStart:
buf
内开始复制的偏移量,默认值为0
。 - sourceEnd:
buf
内停止复制的偏移量(不包括),默认值为buf.length
。 - [callBack]:复制的字节数。
我们刚才使用了 buf.copy
进行了简单的扩容操作,那么 buf.copy
的实现原理是什么呢。
方法实现
首先我们清楚, buf.copy
中一共接受四个参数,分别是 target
、targetStart
、 sourceStart
、 sourceEnd
。
现在我们来看一下完成后的代码。
Buffer.prototype.copy = function (target, targetStart, sourceStart = 0, sourceEnd = this.length) {
for (let i = sourceStart; i < sourceEnd; i++) {
target[targetStart++] = this[i];
}
}
实现了 buf.copy
,我们就可以来看一下 buf.concat
方法了。
Buffer.concat
Buffer.concat(list[, totalLength])
会返回新的 Buffer
,它是将 list
中的所有 Buffer
实例连接在一起的结果。
- list:要拼接的
Buffer
或Uint8Array
实例的数组列表。 - totalLength:连接时
list
中Buffer
实例的总长度。 - callBack:返回一个新的
Buffer
。
在上面的例子中,我们使用 buf.concat
实现了一个 Buffer
扩容的例子。
下面我们就来详解一下它的方法实现。
方法实现
根据 buf.concat
的使用方式,我们可以大概了解到一个思路。 那就是将传入的 Buffer
实例通过 拷贝 的方式将其拼接成一个大的 Buffer
类。
这样我们就可以大概手写出其实现原理了。
Buffer.concat = function (bufferList, len = bufferList.reduce((a, b) => a + b.length, 0)) {
let buffer = Buffer.alloc(len);
// 记录下一次 开始拼接的 位置
let offset = 0;
bufferList.forEach(buf => {
// 判断是不是 Buffer
if (Buffer.isBuffer(buf)) {
buf.copy(buffer, offset);
offset += buf.length;
}
})
return buffer;
}
这样我们就完成了 buf.concat
的实现。
本篇文章由 莫小尚 创作,文章中如有任何问题和纰漏,欢迎您的指正与交流。
您也可以关注我的 个人站点、博客园 和 掘金,我会在文章产出后同步上传到这些平台上。
最后感谢您的支持!