【Node 基础】Buffer & Stream

简介: 【Node 基础】Buffer & Stream

原来来自 我的个人博客

1. Buffer 缓冲区

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

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

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

1. Buffer 和字符串

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

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

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 存储中文:

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

console.log(buf);

打印结果如下:

image.png

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

乱码的问题:

  1. 一般乱码的问题都是因为编码与解码使用了不同的方法而导致的。
  2. Buffer.from() 方法中,我们还可以添加第二个参数来规定使用哪种编码方式
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

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

打印结果如下

image.png

我们也可以对其进行操作

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. 文本的读取
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. 图片的读取
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. 之前我们读取一个文件的信息
fs.readFile("./test.txt", (err, data) => {
  console.log(data.toString()); // Hello World
});

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

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

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

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

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

    打印结果如下:

    image.png

    • 也可以做一些其他的操作:监听其他事件、暂停或恢复
    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. 之前我们写入一个文件的方式是这样的
fs.writeFile('./test.txt',"内容",(err)=>{
})

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

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

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

  • flags:默认是 w,如果我们希望是追加写入,可以使用 a 或者 a+
  • start:写入的位置;
  1. 使用流的方式写入文件
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 方法;

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)
相关文章
|
7月前
|
缓存 JavaScript 前端开发
【Node系列】Buffer详解
JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。 但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。 Node.js中的Buffer是一个全局对象,属于固有(built-in)类型的全局变量,不需要使用require函数导入。它允许直接操作原始内存,主要用于处理二进制数据流。Buffer实例对象的结构和整数数组很像,但Buffer的大小是固定的且在V8堆外分配物理内存。
75 2
|
7月前
|
消息中间件 Web App开发 JavaScript
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
313 0
|
7月前
|
JavaScript 前端开发 API
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)(下)
Node.js【简介、安装、运行 Node.js 脚本、事件循环、ES6 作业队列、Buffer(缓冲区)、Stream(流)】(一)-全面详解(学习总结---从入门到深化)
102 0
|
2月前
|
JavaScript
Node.js Stream(流)
10月更文挑战第4天
33 1
Node.js Stream(流)
|
2月前
|
存储 JSON JavaScript
Node.js Buffer(缓冲区)
10月更文挑战第4天
44 4
|
5月前
|
JavaScript
Node.js 流 Stream【详解】
Node.js 流 Stream【详解】
50 4
|
6月前
|
存储 JSON JavaScript
Node.js Buffer(缓冲区)
Node.js Buffer(缓冲区)
43 1
|
6月前
|
JavaScript
Node.js Stream(流)
Node.js Stream(流)
35 0
|
7月前
|
JavaScript 算法 网络协议
【Node系列】node中的流(Stream)
Node.js 中的流(Stream)是一种处理数据的方式,它允许你以流的方式处理数据,而不是一次性加载整个数据集。这种方式对于处理大量数据非常有用,因为它可以减少内存的使用并提高性能。
79 4
|
7月前
|
JavaScript 网络协议 数据处理
Node.js中的Buffer与Stream:深入解析与使用
【4月更文挑战第30天】本文深入解析了Node.js中的Buffer和Stream。Buffer是处理原始数据的全局对象,适用于TCP流和文件I/O,其大小在V8堆外分配。创建Buffer可通过`alloc`和`from`方法,它提供了读写、切片和转换等操作。Stream是处理流式数据的抽象接口,分为可读、可写、双工和转换四种类型,常用于处理大量数据而无需一次性加载到内存。通过监听事件和调用方法,如读取文件的可读流示例,可以实现高效的数据处理。理解和掌握Buffer及Stream能提升Node.js应用的性能。