一、Stream 的核心价值
Node.js 的 Stream(流)是一种抽象接口,用于高效处理流式数据。其核心优势在于:
- 内存效率:避免一次性加载全部数据到内存,适合处理大文件(如视频、日志)或实时数据(如网络请求、WebSocket 通信)。
- 时间效率:数据可分块传输,处理无需等待完整数据就绪,适合高并发场景。
- 组合性 :通过管道(
pipe()
)将多个流操作串联,形成数据处理链,代码更简洁。
示例对比:
javascript
// 传统方式:一次性读取文件(内存占用高)
const fs = require('fs');
const data = fs.readFileSync('large-file.txt'); // 阻塞且内存爆炸
// 流式处理:分块读取(内存占用低)
const readStream = fs.createReadStream('large-file.txt');
readStream.on('data', (chunk) => {
console.log(`Received ${chunk.length} bytes`);
});
二、Stream 的四种类型
Node.js 的流分为四类,每种类型对应不同的数据操作场景:
类型 | 描述 | 示例 |
---|---|---|
Readable | 数据来源(可读) | fs.createReadStream() |
Writable | 数据目标(可写) | fs.createWriteStream() |
Duplex | 双向流(可读可写) | net.Socket (网络套接字) |
Transform | 转换流(可读可写且可修改数据) | zlib.createGzip() (压缩) |
关键特性:
- Readable :通过事件(
data
、end
)或方法(read()
)触发数据读取。 - Writable :通过
write()
方法写入数据,end()
结束写入。 - Duplex:如 TCP 连接,同时支持发送和接收数据。
- Transform:在数据传输过程中修改数据,如加密、压缩。
三、Stream 的工作模式
Readable 流有两种核心模式,决定数据如何流动:
-
流动模式(Flowing Mode):
-
数据自动通过事件循环推送,触发
data
事件。 -
需手动暂停(
pause()
)或通过pipe()
控制流速。 -
示例 :
javascriptconst stream = fs.createReadStream('file.txt'); stream.on('data', (chunk) => { console.log(chunk.toString()); });
-
-
暂停模式(Paused Mode):
-
需显式调用
read()
方法获取数据。 -
通过
readable
事件或手动轮询触发读取。 -
示例 :
javascriptconst stream = fs.createReadStream('file.txt'); stream.on('readable', () => { let chunk; while ((chunk = stream.read()) !== null) { console.log(chunk.toString()); } });
-
模式切换:
- 注册
data
事件、调用resume()
或pipe()
→ 进入流动模式。 - 调用
pause()
或移除data
事件 → 切换回暂停模式。
四、Stream 的核心方法与事件
1. Readable 流
- 方法 :
read([size])
:手动读取指定大小的数据块。pause()
/resume()
:暂停/恢复数据流动。pipe(destination)
:将数据管道传输到目标流。
- 事件 :
data
:数据块到达时触发。end
:无更多数据时触发。error
:发生错误时触发。
2. Writable 流
- 方法 :
write(chunk[, encoding][, callback])
:写入数据块。end([chunk[, encoding]][, callback])
:结束写入,可选最后一块数据。
- 事件 :
drain
:缓冲区清空,可继续写入时触发。finish
:所有数据写入完成时触发。
3. 管道(pipe()
)
-
作用:连接多个流,形成数据处理链。
-
优势 :
- 自动处理背压(Backpressure):当目标流处理速度慢时,暂停源流。
- 错误传播:任一环节出错会触发整个链的
error
事件。
-
示例 :
javascriptconst fs = require('fs'); const zlib = require('zlib'); // 文件读取 → 压缩 → 写入 fs.createReadStream('input.txt') .pipe(zlib.createGzip()) .pipe(fs.createWriteStream('output.txt.gz')) .on('finish', () => console.log('压缩完成'));
五、Stream 的高级应用
1. 自定义 Transform 流
实现数据转换逻辑(如加密、解析):
javascript
const { Transform } = require('stream');
class UpperCaseTransform extends Transform {
_transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
}
// 使用示例
const upperCaseStream = new UpperCaseTransform();
process.stdin.pipe(upperCaseStream).pipe(process.stdout);
2. 背压(Backpressure)处理
当消费者处理速度慢于生产者时,需控制数据流速:
javascript
const writable = fs.createWriteStream('output.txt');
let pendingChunks = 0;
function writeData(data) {
pendingChunks++;
if (!writable.write(data)) {
writable.once('drain', () => writeNext());
} else {
writeNext();
}
}
function writeNext() {
pendingChunks--;
if (pendingChunks > 0) {
// 继续写入下一块数据
}
}
3. 对象模式(Object Mode)
流默认处理二进制数据(Buffer
/String
),通过 objectMode: true
可传输任意 JavaScript 对象:
javascript
const { Transform } = require('stream');
const objectStream = new Transform({
objectMode: true,
transform(chunk, encoding, callback) {
this.push({ processed: chunk.toString().toUpperCase() });
callback();
}
});
// 输入对象流
objectStream.write({ raw: 'hello' });
objectStream.on('data', (data) => console.log(data)); // { processed: 'HELLO' }
六、Stream 的性能优化
- 合理设置
highWaterMark
:- 控制缓冲区大小(默认 64KB),大文件处理时可适当增大以减少 I/O 操作。
- 避免混合使用事件和方法 :
- 例如,同时监听
data
事件和调用read()
会导致数据重复处理。
- 例如,同时监听
- 错误处理 :
- 始终监听
error
事件,避免未捕获的异常导致进程崩溃。
- 始终监听
- 复用流对象 :
- 避免频繁创建/销毁流,复用可减少内存分配开销。
七、Stream 的常见场景
- 文件操作 :
- 大文件复制、压缩、解压。
- 网络通信 :
- HTTP 请求/响应流式传输(如上传/下载大文件)。
- 实时数据处理 :
- WebSocket 消息流、日志流处理。
- 数据转换 :
- CSV 解析、JSON 序列化/反序列化。
八、总结
Node.js 的 Stream 是处理流式数据的核心工具,通过分块传输和事件驱动机制,显著提升内存效率和响应速度。掌握其类型、模式、方法及管道操作,可优雅解决大文件处理、实时数据流等复杂场景。结合自定义 Transform 流和背压控制,能进一步优化性能,构建高效、可扩展的应用。