Node.js Streams 完全指南:流式处理的艺术
Stream 是 Node.js 的核心超能力:分块处理数据,内存占用与数据规模解耦。本文讲透四种流、管道、错误处理、背压机制与实战案例。
流(Stream)让数据分块流动而非整体加载——处理 10GB 文件内存占用恒定。核心 API 是 readable.pipe(writable):管道自动处理背压(下游慢了上游自动暂停)。四种流:Readable、Writable、Duplex、Transform,pipeline() 是错误安全的管道写法。
为什么流是核心超能力
// 反例:10GB 文件读进内存 → 进程爆炸
const data = fs.readFileSync('huge.log');
// 流式:恒定内存,处理 TB 级也从容
fs.createReadStream('huge.log').pipe(fs.createWriteStream('copy.log'));
Node 的一切 IO 都是流:HTTP 请求/响应、文件读写、TCP socket——理解流才算理解 Node。
四种流
- Readable(可读):数据源——文件读取、HTTP 请求体;
- Writable(可写):数据终点——文件写入、HTTP 响应;
- Duplex(双工):既可读又可写——TCP socket;
- Transform(转换):读写中变换数据——压缩、加密、格式转换。
const { Transform } = require('node:stream');
const upperCase = new Transform({
transform(chunk, encoding, callback) {
callback(null, chunk.toString().toUpperCase());
},
});
process.stdin.pipe(upperCase).pipe(process.stdout);
管道与错误处理
pipe() 的经典缺陷:中间任何流报错,管道不会自动清理其他流。用 pipeline() 替代:
const { pipeline } = require('node:stream/promises');
const zlib = require('node:zlib');
await pipeline(
fs.createReadStream('input.txt'),
zlib.createGzip(),
fs.createWriteStream('input.txt.gz'),
);
// 任一环节出错,全部流正确销毁,错误向上抛出
新项目一律 pipeline,告别裸 pipe。
背压(Backpressure)
下游消费慢于上游生产时,数据在缓冲区堆积——背压机制让上游"等等":write() 返回 false 表示缓冲已满,监听 drain 事件恢复写入。pipe/pipeline 自动处理背压——手动 write 时才需要关心。
实战:大文件处理
const readline = require('node:readline');
// 逐行处理 30GB 日志
const rl = readline.createInterface({ input: fs.createReadStream('access.log') });
let errorCount = 0;
for await (const line of rl) {
if (line.includes('"status":500')) errorCount++;
}
for await 消费流是现代 Node 的最优雅姿势——流即异步迭代器。
性能要点
- 分块大小(highWaterMark):默认 64KB(文件 16KB),吞吐型场景可调大;
- 对象模式(objectMode: true):流传递对象而非 Buffer——任务队列、记录处理用;
- Transform 链保持薄:每段转换都是一次数据拷贝。
常见问题(FAQ)
Q:流和直接读写的性能差多少?
小文件直接读写更快(少一层抽象);大文件优先采用流或分块处理。阈值应按内存预算、并发量和处理方式测量,不采用固定比例。
Q:流的 destroy 和 close 区别?
destroy() 立即销毁并触发 error 处理;end() 优雅结束(写完缓冲再关)。pipeline 出错时自动 destroy 全链。
Q:Node 流和 Web Streams 什么关系?
两套 API 各有历史(Node 流更早),Node 18+ 两者互转(Stream.toWeb/fromWeb)。详见本系列《Node.js Streams 与 Web Streams 对比》。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。