Node.js Stream 深入全面讲解

一、Stream 的核心价值

Node.js 的 Stream(流)是一种抽象接口,用于高效处理流式数据。其核心优势在于:

  1. 内存效率:避免一次性加载全部数据到内存,适合处理大文件(如视频、日志)或实时数据(如网络请求、WebSocket 通信)。
  2. 时间效率:数据可分块传输,处理无需等待完整数据就绪,适合高并发场景。
  3. 组合性 :通过管道(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 :通过事件(dataend)或方法(read())触发数据读取。
  • Writable :通过 write() 方法写入数据,end() 结束写入。
  • Duplex:如 TCP 连接,同时支持发送和接收数据。
  • Transform:在数据传输过程中修改数据,如加密、压缩。
三、Stream 的工作模式

Readable 流有两种核心模式,决定数据如何流动:

  1. 流动模式(Flowing Mode)

    • 数据自动通过事件循环推送,触发 data 事件。

    • 需手动暂停(pause())或通过 pipe() 控制流速。

    • 示例

      javascript 复制代码
      const stream = fs.createReadStream('file.txt');
      stream.on('data', (chunk) => {
        console.log(chunk.toString());
      });
  2. 暂停模式(Paused Mode)

    • 需显式调用 read() 方法获取数据。

    • 通过 readable 事件或手动轮询触发读取。

    • 示例

      javascript 复制代码
      const 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 事件。
  • 示例

    javascript 复制代码
    const 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 的性能优化
  1. 合理设置 highWaterMark
    • 控制缓冲区大小(默认 64KB),大文件处理时可适当增大以减少 I/O 操作。
  2. 避免混合使用事件和方法
    • 例如,同时监听 data 事件和调用 read() 会导致数据重复处理。
  3. 错误处理
    • 始终监听 error 事件,避免未捕获的异常导致进程崩溃。
  4. 复用流对象
    • 避免频繁创建/销毁流,复用可减少内存分配开销。
七、Stream 的常见场景
  1. 文件操作
    • 大文件复制、压缩、解压。
  2. 网络通信
    • HTTP 请求/响应流式传输(如上传/下载大文件)。
  3. 实时数据处理
    • WebSocket 消息流、日志流处理。
  4. 数据转换
    • CSV 解析、JSON 序列化/反序列化。
八、总结

Node.js 的 Stream 是处理流式数据的核心工具,通过分块传输和事件驱动机制,显著提升内存效率和响应速度。掌握其类型、模式、方法及管道操作,可优雅解决大文件处理、实时数据流等复杂场景。结合自定义 Transform 流和背压控制,能进一步优化性能,构建高效、可扩展的应用。

相关推荐
星栈5 小时前
从装一堆工具到看懂 Node 工程化思维:我的项目复盘记录
后端·node.js
东方小月7 小时前
从零开发一个Coding Agent:monorepo项目搭建
前端·后端·node.js
Lihua奏8 小时前
# 用 Node.js 连接数据库:一次请求是怎么跑起来的?
node.js
kisshyshy12 小时前
给端侧大模型装上“发动机”:React 合成事件 + 进度条组件全解
前端·react.js·node.js
csdn2015_15 小时前
nodejs安装
node.js·vue
前端双越老师1 天前
如何以前端视角(非0基础)学 Java ?
java·node.js·全栈
Kel2 天前
Node.js 没那么复杂
人工智能·node.js·全栈
星栈独行2 天前
Node 接口该写同步还是异步?
服务器·开发语言·后端·程序人生·node.js
星栈2 天前
Node 接口该写同步还是异步?
后端·node.js
半个落月2 天前
用 LangChain 连接远程 MCP:从工具发现到多轮调用闭环
人工智能·后端·node.js