一、什么是 Stream?
Node.js 的 Stream 是一种处理流式数据的抽象接口,广泛应用于文件操作、网络通信等场景。
流式数据处理的一个主要优点是可以在数据传输过程中就开始处理数据,而不需要等待整个数据加载完毕,这使得 Node.js 能够高效地处理大量数据,而不会占用过多的内存。
Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对 http 服务器发起请求的request对象就是一个 Stream,还有stdout(标准输出)。
Node.js 的 Stream(流)允许你以流的形式处理数据,而不是一次性将数据全部加载到内存中。这对于处理大量数据或者实现高效的数据传输非常有用。
二、流的四种类型
| 类型 | 说明 |
|---|---|
| Readable(可读流) | 可读操作,如文件读取流、网络请求响应流 |
| Writable(可写流) | 可写操作,如文件写入流、网络请求发送流 |
| Duplex(双工流) | 可读可写操作,如 TCP 套接字 |
| Transform(转换流) | 操作被写入数据,然后读出结果,如压缩/解压缩流 |
常用事件
所有的 Stream 对象都是EventEmitter的实例。常用的事件有:
| 事件 | 说明 |
|---|---|
data | 当有数据可读时触发 |
end | 没有更多的数据可读时触发 |
error | 在接收和写入过程中发生错误时触发 |
finish | 所有数据已被写入到底层系统时触发 |
三、从流中读取数据
常见的可读流包括文件读取流和网络请求响应流。
创建input.txt文件,内容如下:
地址:www.chenbyte.com
创建main.js文件,代码如下:
var fs = require("fs"); var data = ''; // 创建可读流 var readerStream = fs.createReadStream('input.txt'); // 设置编码为 utf8 readerStream.setEncoding('UTF8'); // 处理流事件 --> data, end, and error readerStream.on('data', function(chunk) { data += chunk; }); readerStream.on('end', function() { console.log(data); }); readerStream.on('error', function(err) { console.log(err.stack); }); console.log("程序执行完毕");以上代码执行结果如下:
程序执行完毕 地址:www.chenbyte.com
四、写入流
可写流用于将数据写入目的地,常见的可写流包括文件写入流和网络请求发送流。
创建main.js文件,代码如下:
var fs = require("fs"); var data = '菜鸟教程官网地址:www.runoob.com'; // 创建一个可以写入的流,写入到文件 output.txt 中 var writerStream = fs.createWriteStream('output.txt'); // 使用 utf8 编码写入数据 writerStream.write(data, 'UTF8'); // 标记文件末尾 writerStream.end(); // 处理流事件 --> finish、error writerStream.on('finish', function() { console.log("写入完成。"); }); writerStream.on('error', function(err) { console.log(err.stack); }); console.log("程序执行完毕");以上程序会将data变量的数据写入到output.txt文件中。代码执行结果如下:
$ node main.js 程序执行完毕 写入完成。
查看output.txt文件的内容:
$ cat output.txt 地址:www.chenbyte.com
五、双工流(Duplex)
双工流同时具有可读和可写的能力。一个典型的双工流是 TCP 套接字。
const net = require('net'); // 创建一个 TCP 服务器 const server = net.createServer((socket) => { console.log('Client connected.'); // 读取客户端数据 socket.on('data', (data) => { console.log('Received data:', data.toString()); }); // 向客户端发送数据 socket.write('Hello, Client!\n'); // 监听关闭事件 socket.on('end', () => { console.log('Client disconnected.'); }); }); server.listen(3000, () => { console.log('Server listening on port 3000.'); });六、转换流(Transform)
转换流是一种特殊的双工流,可以修改或转换数据。常见的转换流包括压缩和解压缩流。
const zlib = require('zlib'); const fs = require('fs'); // 创建一个可读流 const readableStream = fs.createReadStream('example.txt'); // 创建一个转换流(压缩) const gzip = zlib.createGzip(); // 创建一个可写流 const writableStream = fs.createWriteStream('example.txt.gz'); // 将可读流管道到转换流,再管道到可写流 readableStream.pipe(gzip).pipe(writableStream); // 监听完成事件 writableStream.on('finish', () => { console.log('File compressed successfully.'); });七、管道流(Pipe)
管道提供了一个输出流到输入流的机制。
通常我们用于从一个流中获取数据并将数据传递到另外一个流中。
把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢实现了大文件的复制过程。
管道流操作实例
设置input.txt文件内容如下:
菜鸟教程官网地址:www.chenbyte.com
创建main.js文件,代码如下:
var fs = require("fs"); // 创建一个可读流 var readerStream = fs.createReadStream('input.txt'); // 创建一个可写流 var writerStream = fs.createWriteStream('output.txt'); // 管道读写操作 // 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中 readerStream.pipe(writerStream); console.log("程序执行完毕");代码执行结果如下:
$ node main.js 程序执行完毕
查看output.txt文件的内容:
$ cat output.txt 地址:www.chenbyte.com
八、链式流
链式是通过连接输出流到另外一个流并创建多个流操作链的机制。
链式流一般用于管道操作。接下来我们使用管道和链式来压缩和解压文件。
压缩文件
创建compress.js文件,代码如下:
var fs = require("fs"); var zlib = require('zlib'); // 压缩 input.txt 文件为 input.txt.gz fs.createReadStream('input.txt') .pipe(zlib.createGzip()) .pipe(fs.createWriteStream('input.txt.gz')); console.log("文件压缩完成。");代码执行结果如下:
$ node compress.js 文件压缩完成。
执行完以上操作后,可以看到当前目录下生成了input.txt的压缩文件input.txt.gz。
解压文件
创建decompress.js文件,代码如下:
var fs = require("fs"); var zlib = require('zlib'); // 解压 input.txt.gz 文件为 input.txt fs.createReadStream('input.txt.gz') .pipe(zlib.createGunzip()) .pipe(fs.createWriteStream('input.txt')); console.log("文件解压完成。");代码执行结果如下:
$ node decompress.js 文件解压完成。
九、暂停和恢复
可读流可以暂停和恢复数据的读取。
const fs = require('fs'); const readableStream = fs.createReadStream('example.txt', 'utf8'); readableStream.on('data', (chunk) => { console.log('Received chunk:', chunk); readableStream.pause(); // 暂停读取 setTimeout(() => { readableStream.resume(); // 恢复读取 }, 1000); });十、销毁流
可以销毁流,释放资源。
const fs = require('fs'); const readableStream = fs.createReadStream('example.txt', 'utf8'); readableStream.on('data', (chunk) => { console.log('Received chunk:', chunk); readableStream.destroy(); // 销毁流 });十一、流的主要方法
| 方法 | 说明 |
|---|---|
readable.pipe(destination) | 将可读流的数据传输到可写流 |
readable.on('data', callback) | 监听数据事件 |
readable.on('end', callback) | 监听结束事件 |
readable.pause() | 暂停可读流 |
readable.resume() | 恢复可读流 |
readable.destroy() | 销毁可读流 |
writable.write(chunk) | 写入数据到可写流 |
writable.end() | 结束写入 |
writable.on('finish', callback) | 监听写入完成事件 |
writable.on('error', callback) | 监听错误事件 |
十二、本章小结
| 知识点 | 说明 |
|---|---|
| Stream 是什么 | 处理流式数据的抽象接口,高效处理大量数据 |
| 四种流类型 | Readable(可读)、Writable(可写)、Duplex(双工)、Transform(转换) |
| 常用事件 | data、end、error、finish |
| 管道流 | pipe()方法将可读流连接到可写流 |
| 链式流 | 多个pipe()串联,形成处理链 |
| 压缩/解压 | 使用zlib模块配合转换流实现 |
| 控制流 | pause()、resume()、destroy()控制流的行为 |
提示:Stream 是 Node.js 高效处理 I/O 的核心机制。对于大文件处理、网络数据传输等场景,使用 Stream 可以显著降低内存占用,提升应用性能。管道流(
pipe)是 Stream 中最常用的操作模式,可以简洁地将数据从源头传递到目的地。