原来来自 我的个人博客
1. Buffer 缓冲区
我们会发现,对于前端开发来说,通常很少会和二进制直接打交道,但是对于服务器端为了做很多的功能,我们必须直接去操作其二进制的数据;
所以 Node 为了可以方便开发者完成更多功能,提供给了我们一个 Buffer类用来创建一个专门存放二进制数据的缓存区,并且它是全局的。
我们可以将 Buffer 看成是一个存储二进制的数组,这个数组中的每一项,可以保存 8 位二进制:0000 0000
1. Buffer 和字符串
Buffer 相当于是一个字节的数组,数组中的每一项对应一个字节的大小:
如果我们希望将一个字符串放入到 Buffer 中,是怎么样的过程呢?
const buffer = new Buffer("hello");
console.log(buffer);
打印结果如下:

可以看到打印出的 Buffer 对象包含了五个 由两位十六进制组成的数字。而我们知道 2 位十六进制正好对应 8 位二进制也就是一个字节
而如果你去查询 ASCII 表的话会发现,这五个十六进制数正好对应着英文字母的 h e l l o

也就是说,我们可以得出结论:Buffer 存储字符串时默认是以 ASCII 将字符串编码存储的
1.2 Buffer 与中文
Buffer 默认是以 utf-8 为默认编码存储中文的
在上面一小节的例子中,我们使用 new Buffer() 创建了一个 Buffer 对象,事实上由于安全性和可用性的问题,Node 已经废弃了这种方法,推荐使用 Buffer.alloc()、Buffer.allocUnsafe() 或者 Buffer.from() 这三种方法创建 Buffer 对象
接下来我们试着用 Buffer 存储中文:
const buf = Buffer.from("你好");
console.log(buf);
打印结果如下:

我们知道在 UTF-8 中,一个中文等于三个字节。而在上面的打印结果也能看出 “你好” 两个字正好存储了六个两位十六进制的数字,证实了我们的结论。
乱码的问题:
- 一般乱码的问题都是因为编码与解码使用了不同的方法而导致的。
- 在
Buffer.from()方法中,我们还可以添加第二个参数来规定使用哪种编码方式
const fs = require("fs");
const buf = Buffer.from("你好", "utf16le");
console.log(buf.toString("utf16le"));
console.log(buf.toString("utf8"));
打印结果:

可以看到,同时以 utf-16 解码打印出来没有出现乱码,而以 utf-8 解码打印出来出现了乱码。
1.3 Buffer 的其他创建方式
关于 Buffer 的创建方式还有很多,见下图

这里主要再讲一下 alloc,如果有学习 c 语言的应该是很熟悉的。它的意思是 承认、同意。Buffer.alloc 的作用为向内存申请一个多长的 Buffer,里面的默认数据是 00。
const buf = Buffer.alloc(8);
console.log(buf);
打印结果如下

我们也可以对其进行操作
buf[0] = "w".charCodeAt();
buf[1] = 100;
buf[2] = 0x66;
console.log(buf); // <Buffer 77 64 66 00 00 00 00 00>
console.log(buf[0]); // 119
console.log(buf[2].toString()); // 102
1.4 Buffer 和文件读取
在上一章 fs 模块中介绍中,我们提到了 readFile 的回调是一个 Buffer,现在大家应该也能理解了吧
- 文本的读取
const fs = require("fs");
fs.readFile("./test.txt", (err, data) => {
console.log(data); // <Buffer 48 65 6c 6c 6f 20 57 6f 72 6c 64>
console.log(data.toString()); // Hello World
});
- 图片的读取
const fs = require("fs");
fs.readFile("./image.png", (err, data) => {
console.log(data); // <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 ... 1878012 more bytes>
});
2. Stream 流
Stream 意为 小溪、小河的意思,在编程中通常翻译为流。
我们可以想象当我们从一个文件中读取数据时,文件的二进制数据会源源不断的被读取到我们程序中,而这个一连串的字节,就是我们程序中的流。
所以我们可以这样理解流:
- 它是连续字节的一种表现形式和抽象概念
- 流应该是可读的,也是可写的
再上一章讲文件的读写时,我们已经可以直接通过 readFile 和 writeFile 的方式读写文件了,为什么还需要流呢?
- 直接读写的方式,虽然简单,但是无法控制一些细节的操作
- 比如从什么位置开始读、读到什么位置、一次性读取多少个字节
- 读到某个位置后,暂停读取,某个时刻恢复继续读取等等
- 或者这个文件非常大,比如一个视频文件,一次性全部读取并不合适
Node.js 中有四种基本流类型:
Writable:可以向其写入数据的流(例如fs.createWriteStream())。Readable:可以从中读取数据的流(例如fs.createReadStream())。Duplex:同时为Readable和Writable(例如net.Socket)。Transform:Transform可以在写入和读取数据时修改或转换数据的流(例如zlib.createDeflate())。
这里我们通过 fs 的操作,讲解一下 Writable、Readable,另外两个大家可以自行学习一下。
2.1 Readable
- 之前我们读取一个文件的信息
fs.readFile("./test.txt", (err, data) => {
console.log(data.toString()); // Hello World
});
这种方式是一次性将一个文件中所有的内容都读取到程序(内存)中,但是这种读取方式就会出现我们之前提到的很多问题:
- 文件过大、读取的位置、结束的位置、一次读取的大小;
这个时候,我们可以使用 createReadStream,我们来看几个参数,更多参数可以参考官网:
start:文件读取开始的位置;end:文件读取结束的位置;highWaterMark:一次性读取字节的长度,默认是64kb;
通过
Readable读取一个文件信息- 创建文件的Readable
const read = fs.createReadStream('./test.txt', { start: 3, end: 8, highWaterMark: 4 })- 通过监听 data 时间,获取读取到的数据
read.on("data", (data) => { console.log(data); });打印结果如下:

- 也可以做一些其他的操作:监听其他事件、暂停或恢复
read.on("open", (fd) => { console.log("文件被打开", fd); }); read.on("end", () => { console.log("文件读取结束"); }); read.on("close", () => { console.log("文件被关闭"); }); // 暂停读取,两秒后再读取 read.pause(); setTimeout(() => { read.resume(); }, 2000);
2.2 Writeable
- 之前我们写入一个文件的方式是这样的
fs.writeFile('./test.txt',"内容",(err)=>{
})
这种方式相当于一次性将所有的内容写入到文件中,但是这种方式也有很多问题:
- 比如我们希望一点点写入内容,精确每次写入的位置等;
这个时候,我们可以使用 createWriteStream,我们来看几个参数,更多参数可以参考官网:
flags:默认是w,如果我们希望是追加写入,可以使用a或者a+;start:写入的位置;
- 使用流的方式写入文件
const writer = fs.createWriteStream("./test.txt", {
flags: "a+",
start: 8,
});
writer.write("你好啊", (err) => {
console.log("写入成功");
});
writer.on("open", () => {
console.log("文件打开了");
});
writer.on("finish", () => {
console.log("文件写入结束");
});
writer.on("close", () => {
console.log("文件关闭");
});
我们会发现,我们并不能监听到 close 事件:
- 这是因为写入流在打开后是不会自动关闭的;
- 我们必须手动关闭,来告诉
Node已经写入结束了; - 并且会发出一个
finish事件的;
另外一个非常常用的方法是 end:end 方法相当于做了两步操作: write 传入的数据和调用close 方法;
writer.close()
writer.end('Hello World')
2.3 pipe 方法
正常情况下,我们可以将读取到的 输入流,手动的放到 输出流中进行写入:
const reader = fs.createReadStream("./test.txt");
const writer = fs.createWriteStream("./bar.txt");
reader.on("data", (data) => {
console.log(data);
writer.write(data, (err) => {
console.log(err);
});
});
我们也可以通过 pipe 来完成这样的操作:
reader.pipe(writer)