Node.js 流式处理大文件实战与踩坑

发布于2026-09-02 14:25 阅读25次 分享 Node.js 用流式方式处理大文件的实战经验,涵盖可读流、可写流、管道、背压处理、断点续读、编码陷阱等内容,附代码示例和踩坑记录,避免大文件处理撑爆内存。
# Node.js 流式处理大文件实战与踩坑
> 一次性读取几个 GB 的文件,内存直接爆掉。流式处理是唯一解,但坑也不少。本文分享实战经验和踩过的坑。
## 一、为什么必须用流
```js
// 错误示范:一次性读入内存
const data = fs.readFileSync("big.log");
// 正确做法:流式逐块处理
const stream = fs.createReadStream("big.log");
```
readFileSync 会把整个文件加载进内存,几个 GB 的文件直接 OOM。流式是边读边处理,内存占用恒定。
## 二、核心 API 速览
### 可读流 + 逐行处理
```js
const readline = require("readline");
const fs = require("fs");
const rl = readline.createInterface({
input: fs.createReadStream("access.log"),
crlfDelay: Infinity
});
rl.on("line", (line) => {
// 逐行处理,内存占用极低
if (line.includes("ERROR")) console.log(line);
});
```
### 管道传输
```js
const src = fs.createReadStream("input.gz");
const dest = fs.createWriteStream("output.txt");
src.pipe(zlib.createGunzip()).pipe(dest);
```
## 三、管道与背压
> 注意坑1:忽略背压(Backpressure)
```js
// 错误:消费者慢,生产者快,内存越积越多
src.on("data", (chunk) => {
slowWrite(chunk); // 写入很慢
});
// 正确:用 pipe 自动处理背压
src.pipe(slowWritable);
```
pipe 内部会处理背压:消费者处理不过来时自动暂停源流。手动监听 data 事件则没有这个保护。
## 四、常见踩坑
> 坑2:编码问题
```js
// 中文可能乱码,需要显式指定编码
fs.createReadStream("data.txt", { encoding: "utf8" });
```
> 坑3:流未关闭导致句柄泄漏
```js
// 用完必须关闭,或用 pipeline 自动管理
const { pipeline } = require("stream");
pipeline(src, dest, (err) => {
if (err) console.error("管道失败:", err);
else console.log("完成");
});
```
pipeline 会在出错或完成时自动销毁所有流,避免资源泄漏。
> 坑4:分块边界截断
```js
// chunk 大小不固定,可能从汉字中间切断
src.on("data", (chunk) => {
console.log(chunk.toString()); // 可能有乱码
});
// 用 StringDecoder 解决
const { StringDecoder } = require("string_decoder");
const decoder = new StringDecoder("utf8");
src.on("data", (chunk) => {
console.log(decoder.write(chunk));
});
```
## 五、实战:大文件行数统计
```js
const readline = require("readline");
const fs = require("fs");
async function countLines(file) {
const rl = readline.createInterface({
input: fs.createReadStream(file),
crlfDelay: Infinity
});
let count = 0;
for await (const line of rl) {
count++;
}
return count;
}
countLines("huge.log").then((n) => console.log("总行数:", n));
```
## 六、内存对比实测
- 一次性读入 2GB 文件:内存峰值约 2GB 以上,极易崩溃
- 流式逐行处理:内存峰值约 30-50MB,稳定运行
结论:处理大文件,务必用流。
## 七、小结
记住三条原则:
1. 能流式就别整读
2. 能用 pipe/pipeline 就别手动 data 事件
3. 涉及编码转换用 StringDecoder
做到这三点,大文件处理稳如磐石。