学习笔记—Buffer的常用方法与实现

日常的学习笔记,包括 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。 如果 fillundefined,则 Buffer 将以零填充。

  • size:新的 Buffer 所需的长度。
  • fill:用于预填充新 Buffer 的值,默认值为 0
  • encoding:如果 fill 是字符串,则这就是它的编码。默认值为 utf8
1
2
3
4
5
// 创建了一个指定长度的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) 使用 0255 范围内的字节 array 分配新的 Buffer。 该范围之外的数组条目将被截断以符合它。

1
2
3
4
5
6
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 的新 Bufferencoding 参数标识将 string 转换为字节时要使用的字符编码。

  • string: 要编码的字符串。
  • encodingstring 的编码,默认值为 utf8
1
2
let buf = Buffer.from('莫小尚');
console.log(buf); // <Buffer e8 8e ab e5 b0 8f e5 b0 9a>

Buffer.from(string) 是目前 Buffer 经常使用的方法。这个方法可以存储数据,存储的数据可以用 Buffer 进行表示。同时也可以和字符串之间进行相互转化。

1
2
3
4
// 使用 .toString() 方法,将buffer转换成字符串
console.log(buf.toString()); // 莫小尚
// 可以转换成任意指定编码
console.log(buf.toString('base64')); // 6I6r5bCP5bCa

buffer.toString() 默认值为 utf8

我们在进行读写操作时,如果不指定编码,则所有读取的文件内容都是 buffer 类型。

1
2
3
4
5
6
// 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 进行扩容。

1
2
3
4
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 中。

1
2
3
buf1.copy(bigBuf, 0, 0, buf1.length);
buf2.copy(bigBuf, buf1.length, 0, buf2.length);
console.log(bigBuf.toString()); // 莫小尚

这里我们使用到了 buf.copy() 方法,稍后会进行讲解。

这样我们就完成了一个简单的扩容操作。 (注:在实际工作中,此方法并不常用。我们一般会使用 buf.concat 来进行扩容操作。)

1
2
3
4
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:被拷贝的 BufferUint8Array,也就是我们的 大容量 Buffer
  • targetStarttarget 内开始写入的偏移量,默认值为 0
  • sourceStartbuf 内开始复制的偏移量,默认值为 0
  • sourceEndbuf 内停止复制的偏移量(不包括),默认值为 buf.length
  • [callBack]:复制的字节数。

我们刚才使用了 buf.copy 进行了简单的扩容操作,那么 buf.copy 的实现原理是什么呢。

方法实现

首先我们清楚, buf.copy 中一共接受四个参数,分别是 targettargetStartsourceStartsourceEnd

现在我们来看一下完成后的代码。

1
2
3
4
5
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:要拼接的 BufferUint8Array 实例的数组列表。
  • totalLength:连接时 listBuffer 实例的总长度。
  • callBack:返回一个新的 Buffer

在上面的例子中,我们使用 buf.concat 实现了一个 Buffer 扩容的例子。

下面我们就来详解一下它的方法实现。

方法实现

根据 buf.concat 的使用方式,我们可以大概了解到一个思路。 那就是将传入的 Buffer 实例通过 拷贝 的方式将其拼接成一个大的 Buffer 类。

这样我们就可以大概手写出其实现原理了。

1
2
3
4
5
6
7
8
9
10
11
12
13
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 的实现。

本篇文章由 莫小尚 创作,文章中如有任何问题和纰漏,欢迎您的指正与交流。
您也可以关注我的 个人站点博客园掘金,我会在文章产出后同步上传到这些平台上。
最后感谢您的支持!

请打赏并支持一下作者吧~

欢迎关注我的微信公众号