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 流和背压控制,能进一步优化性能,构建高效、可扩展的应用。

相关推荐
实习生小黄4 小时前
express 连接在线数据库踩坑
node.js·express
伍哥的传说8 小时前
H3初识——入门介绍之常用中间件
前端·javascript·react.js·中间件·前端框架·node.js·ecmascript
超级土豆粉9 小时前
npm 包 scheduler 介绍
前端·npm·node.js
WildBlue9 小时前
流式输出:前端工程师的魔术表演,小白也能看懂!🎩✨
前端·javascript·node.js
爱分享的程序员1 天前
前端面试专栏-算法篇:20. 贪心算法与动态规划入门
前端·javascript·node.js
我叫黑大帅1 天前
Sequelize:让你和数据库唠嗑像聊微信一样简单 😎
后端·node.js
小山不高1 天前
本地使用minio之前后端关键点
前端·node.js
吓死羊了1 天前
设置nginx和tomcat开机自动启动
后端·node.js·tomcat
归于尽1 天前
浏览器和 Node.js 的 EventLoop,原来差别这么大
前端·node.js·浏览器