深入 Node.js Stream 流:处理大数据的利器
Stream 是 Node.js 最强大但最被低估的特性之一。它让你能够以内存高效的方式处理大量数据。
为什么需要 Stream?
// ❌ 传统方式:将整个文件读入内存
const data = fs.readFileSync('huge-file.csv'); // 可能 OOM!
processData(data);
// ✅ Stream 方式:逐块处理
fs.createReadStream('huge-file.csv')
.pipe(csvParser())
.pipe(transformStream())
.pipe(fs.createWriteStream('output.csv'));
四种 Stream 类型
| 类型 | 功能 | 示例 |
|---|---|---|
| Readable | 读取数据 | fs.createReadStream() |
| Writable | 写入数据 | fs.createWriteStream() |
| Duplex | 读写 | net.Socket |
| Transform | 转换数据 | zlib.createGzip() |
Readable Stream
const { Readable } = require('stream');
// 自定义可读流
class NumberGenerator extends Readable {
constructor(max, options) {
super(options);
this.max = max;
this.current = 0;
}
_read() {
if (this.current < this.max) {
this.push(String(this.current++));
} else {
this.push(null); // 结束
}
}
}
const numbers = new NumberGenerator(1000000);
// 两种消费方式
// 方式 1:pipe
numbers.pipe(process.stdout);
// 方式 2:事件
numbers.on('data', chunk => console.log(chunk));
numbers.on('end', () => console.log('Done'));
Transform Stream
const { Transform } = require('stream');
class UppercaseTransform extends Transform {
_transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
}
// 使用
fs.createReadStream('input.txt')
.pipe(new UppercaseTransform())
.pipe(fs.createWriteStream('output.txt'));
Pipeline
const { pipeline } = require('stream/promises');
async function processFile() {
await pipeline(
fs.createReadStream('input.csv'),
csvParser(),
filterStream(row => row.active),
transformStream(row => ({ ...row, processed: true })),
stringify(),
fs.createWriteStream('output.csv')
);
console.log('Pipeline completed');
}
背压处理
当写入速度跟不上读取速度时,Stream 自动处理背压:
const readable = getFastReadable();
const writable = getSlowWritable();
// pipe 自动处理背压
readable.pipe(writable);
// 手动处理背压
readable.on('data', chunk => {
const canContinue = writable.write(chunk);
if (!canContinue) {
readable.pause(); // 暂停读取
writable.once('drain', () => readable.resume()); // 等待排空
}
});
实战:处理 10GB CSV 文件
async function processLargeCSV(inputPath, outputPath) {
const lineCount = { value: 0 };
await pipeline(
fs.createReadStream(inputPath),
split2(), // 按行分割
new Transform({
objectMode: true,
transform(line, enc, cb) {
lineCount.value++;
if (lineCount.value % 100000 === 0) {
console.log(`Processed ${lineCount.value} lines`);
}
const processed = processLine(line.toString());
cb(null, processed + '\n');
}
}),
fs.createWriteStream(outputPath)
);
}
总结
Stream 的核心价值:
- 内存高效:逐块处理,不会 OOM
- 时间高效:边读边处理,无需等待全部读完
- 可组合:通过 pipe 和 pipeline 连接
- 自动背压:防止内存溢出