2023-02-24
Nodejs
00

目录

1. Buffer 缓冲区
1. Buffer 和字符串
1.2 Buffer 与中文
1.3 Buffer 的其他创建方式
1.4 Buffer 和文件读取
2. Stream 流
2.1 Readable
2.3 pipe 方法

1. Buffer 缓冲区

我们会发现,对于前端开发来说,通常很少会和二进制直接打交道,但是对于服务器端为了做很多的功能,我们必须直接去操作其二进制的数据;

所以 Node 为了可以方便开发者完成更多功能,提供给了我们一个 Buffer类用来创建一个专门存放二进制数据的缓存区,并且它是全局的。

我们可以将 Buffer 看成是一个存储二进制的数组,这个数组中的每一项,可以保存 8 位二进制:0000 0000

1. Buffer 和字符串

Buffer 相当于是一个字节的数组,数组中的每一项对应一个字节的大小

如果我们希望将一个字符串放入到 Buffer 中,是怎么样的过程呢?

js
const buffer = new Buffer("hello"); console.log(buffer);

打印结果如下:

image.png

可以看到打印出的 Buffer 对象包含了五个 由两位十六进制组成的数字。而我们知道 2 位十六进制正好对应 8 位二进制也就是一个字节

而如果你去查询 ASCII 表的话会发现,这五个十六进制数正好对应着英文字母的 h e l l o

image.png

也就是说,我们可以得出结论:Buffer 存储字符串时默认是以 ASCII 将字符串编码存储的

1.2 Buffer 与中文

Buffer 默认是以 utf-8 为默认编码存储中文的

在上面一小节的例子中,我们使用 new Buffer() 创建了一个 Buffer 对象,事实上由于安全性和可用性的问题,Node 已经废弃了这种方法,推荐使用 Buffer.alloc()Buffer.allocUnsafe() 或者 Buffer.from() 这三种方法创建 Buffer 对象

接下来我们试着用 Buffer 存储中文:

js
const buf = Buffer.from("你好"); console.log(buf);

打印结果如下:

image.png

我们知道在 UTF-8 中,一个中文等于三个字节。而在上面的打印结果也能看出 “你好” 两个字正好存储了六个两位十六进制的数字,证实了我们的结论。

乱码的问题:

  1. 一般乱码的问题都是因为编码与解码使用了不同的方法而导致的。
  2. Buffer.from() 方法中,我们还可以添加第二个参数来规定使用哪种编码方式
js
const fs = require("fs"); const buf = Buffer.from("你好", "utf16le"); console.log(buf.toString("utf16le")); console.log(buf.toString("utf8"));

打印结果:

image.png

可以看到,同时以 utf-16 解码打印出来没有出现乱码,而以 utf-8 解码打印出来出现了乱码。

1.3 Buffer 的其他创建方式

关于 Buffer 的创建方式还有很多,见下图

image.png

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

js
const buf = Buffer.alloc(8); console.log(buf);

打印结果如下

image.png

我们也可以对其进行操作

js
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,现在大家应该也能理解了吧

  1. 文本的读取
js
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 });
  1. 图片的读取
js
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 意为 小溪、小河的意思,在编程中通常翻译为流。

我们可以想象当我们从一个文件中读取数据时,文件的二进制数据会源源不断的被读取到我们程序中,而这个一连串的字节,就是我们程序中的流。

所以我们可以这样理解流:

  1. 它是连续字节的一种表现形式和抽象概念
  2. 流应该是可读的,也是可写的

再上一章讲文件的读写时,我们已经可以直接通过 readFile 和 writeFile 的方式读写文件了,为什么还需要流呢?

  1. 直接读写的方式,虽然简单,但是无法控制一些细节的操作
  2. 比如从什么位置开始读读到什么位置一次性读取多少个字节
  3. 读到某个位置后,暂停读取某个时刻恢复继续读取等等
  4. 或者这个文件非常大,比如一个视频文件,一次性全部读取并不合适

Node.js 中有四种基本流类型:

  1. Writable:可以向其写入数据的流(例如 fs.createWriteStream())。
  2. Readable:可以从中读取数据的流(例如 fs.createReadStream())。
  3. Duplex:同时为 ReadableWritable(例如 net.Socket)。
  4. TransformTransform 可以在写入和读取数据时修改或转换数据的流(例如zlib.createDeflate())。

这里我们通过 fs 的操作,讲解一下 WritableReadable,另外两个大家可以自行学习一下。

2.1 Readable

  1. 之前我们读取一个文件的信息
js
fs.readFile("./test.txt", (err, data) => { console.log(data.toString()); // Hello World });

这种方式是一次性将一个文件中所有的内容都读取到程序(内存)中,但是这种读取方式就会出现我们之前提到的很多问题:

  • 文件过大、读取的位置、结束的位置、一次读取的大小;

这个时候,我们可以使用 createReadStream,我们来看几个参数,更多参数可以参考官网:

  • start:文件读取开始的位置;
  • end:文件读取结束的位置;
  • highWaterMark:一次性读取字节的长度,默认是 64kb
  1. 通过 Readable 读取一个文件信息

    • 创建文件的Readable
    js
    const read = fs.createReadStream('./test.txt', { start: 3, end: 8, highWaterMark: 4 })
    • 通过监听 data 时间,获取读取到的数据
    js
    read.on("data", (data) => { console.log(data); });

    打印结果如下:

    image.png

    • 也可以做一些其他的操作:监听其他事件、暂停或恢复
    js
    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

  1. 之前我们写入一个文件的方式是这样的
js
fs.writeFile('./test.txt',"内容",(err)=>{ })

这种方式相当于一次性将所有的内容写入到文件中,但是这种方式也有很多问题:

  • 比如我们希望一点点写入内容,精确每次写入的位置等;

这个时候,我们可以使用 createWriteStream,我们来看几个参数,更多参数可以参考官网:

  • flags:默认是 w,如果我们希望是追加写入,可以使用 a 或者 a+

  • start:写入的位置;

  1. 使用流的方式写入文件
js
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 事件:

  1. 这是因为写入流在打开后是不会自动关闭的;

  2. 我们必须手动关闭,来告诉 Node 已经写入结束了;

  3. 并且会发出一个 finish 事件的;

另外一个非常常用的方法是 endend 方法相当于做了两步操作: write 传入的数据和调用close 方法;

js
writer.close() writer.end('Hello World')

2.3 pipe 方法

正常情况下,我们可以将读取到的 输入流,手动的放到 输出流中进行写入:

js
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 来完成这样的操作:

js
reader.pipe(writer)
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:叶继伟

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!