Node.js入门教程(十八):Stream(流)

一、什么是 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(转换)
常用事件 dataenderrorfinish
管道流 pipe() 方法将可读流连接到可写流
链式流 多个 pipe() 串联,形成处理链
压缩/解压 使用 zlib 模块配合转换流实现
控制流 pause()resume()destroy() 控制流的行为

提示 :Stream 是 Node.js 高效处理 I/O 的核心机制。对于大文件处理、网络数据传输等场景,使用 Stream 可以显著降低内存占用,提升应用性能。管道流(pipe)是 Stream 中最常用的操作模式,可以简洁地将数据从源头传递到目的地。

相关推荐
东方小月19 小时前
从零开发一个 Coding Agent(九):实现 Agent 的工具调用闭环
人工智能·前端框架·node.js
Yolanda_20221 天前
nvm安装Node卡顿解决方案:两行配置切换国内镜像源
node.js
王八八。1 天前
nvm 安装与配置完全指南:从零到多版本 Node.js 自由切换
node.js
紫禁玄科1 天前
Shai-Hulud:npm生态的自我复制蠕虫风暴
前端·npm·node.js
烂蜻蜓2 天前
Node.js入门教程(十四):async/await
node.js
谢小飞4 天前
大文件上传很难?学会这招,GB文件秒传不是梦
前端·node.js
周小董4 天前
[1367]npm 安装 canvas 报错 node-gyp ERR
前端·npm·node.js
张龙6875 天前
Node.js 内存泄漏排查实战:从内存曲线到堆快照,一条可复用的定位路径
性能优化·node.js
烂蜻蜓5 天前
Node.js入门教程(七):工作机制
node.js