Nodejs-HardCore: 流类型、应用与内置类型实战

流的类型

流是Node.js中处理流式数据的核心抽象概念,其本质是将数据视为连续流动的字节序列而非整体块。流在Node.js中无处不在,以下是各类流实现:

流的类型

文件流

HTTP流

解析器流

浏览器流

音频流

RPC流

测试流

控制/元/状态流

  • 文件流 (fs.createReadStream):高效读取大文件,避免内存溢出
  • HTTP流:处理请求和响应,支持分块传输编码
  • 解析器流:JSON解析器、XML解析器等流式处理
  • 浏览器流:Fetch API中的ReadableStream
  • 音频流:WebRTC、音频处理库中的流
  • RPC远程调用:gRPC等框架中的双向流通信
  • 测试流:模拟流接口进行单元测试
  • 控制/元/状态流:抽象流的高级应用(如RxJS)

James Halliday的stream-handbook是理解Node.js流的权威资源,详细解释了流的哲学和实践

API 示例

// 经典流类型示例 
const { Readable, Writable, Duplex, Transform } = require('stream');
类型典型场景代表模块
Readable文件读取/HTTP请求fs.createReadStream
Writable文件写入/HTTP响应fs.createWriteStream
DuplexTCP套接字net.Socket
Transform数据压缩/加密zlib.createGzip

什么时候使用流


示例

// 内存对比:传统方式 vs 流式处理 
const fs = require('fs');
 
// 传统方式(内存占用高)
fs.readFile('1GB-file.zip', (err, data) => { /* 需加载整个文件到内存 */ });
 
// 流式处理(固定内存占用)
fs.createReadStream('1GB-file.zip')
  .pipe(fs.createWriteStream('copy.zip'));

流在以下场景中具有显著优势:

  1. 处理大型文件(GB级日志文件)
  2. 实时数据处理(视频转码、实时分析)
  3. 网络通信(HTTP请求/响应)
  4. 内存敏感应用(IoT设备)
  5. 管道操作(多步骤数据处理)

流式API的核心价值在于高效内存利用:传统方式读取1GB文件需要1GB内存,而流式处理只需几KB缓冲区

老版与新版的流对比

老版流

新版流

Node.js 0.10

非对象模式

手动处理数据块

复杂的错误处理

Node.js 4.0+

对象模式支持

简化API

标准错误处理

管道自动管理


特性老版流 (0.10-)新版流 (4.0+)
API设计事件驱动为主继承Stream基类
对象模式不支持支持任意JavaScript对象
错误处理需手动监听’error’事件自动传播管道错误
内存控制手动管理背压自动背压控制
默认行为流默认不流动可读流默认自动流动

第三方模块中的流


以下流行模块深度集成流:

  1. JSONStream:流式JSON解析
  2. csv-parser:大型CSV处理
  3. Socket.IO:实时双向通信
  4. Express:中间件流处理
  5. Webpack:构建管道
// 使用csv-parser处理大型CSV 
const csv = require('csv-parser');
fs.createReadStream('data.csv')
  .pipe(csv())
  .on('data', (row) => console.log(row))
  .on('end', () => console.log('CSV处理完成'));

流继承事件


Node.js流继承自EventEmitter,核心事件包括:

可读流事件:

  • readable:有新数据可读取
  • data:数据块可用(自动流动模式)
  • end:无更多数据
  • error:发生错误
  • close:底层资源关闭

可写流事件:

  • drain:缓冲区空可继续写入
  • finish:所有数据已刷新到底层
  • pipe/unpipe:管道连接/断开
// 事件处理示例
readableStream
  .on('data', (chunk) => {
    if (!writableStream.write(chunk)) {
      readableStream.pause(); // 背压控制 
    }
  })
  .on('end', () => writableStream.end());

再来看下这个:

readableStream 
  .on('readable', () => console.log('数据可读'))
  .on('end', () => console.log('数据结束'))
  .on('error', (err) => console.error('流错误', err));
 
writableStream 
  .on('finish', () => console.log('写入完成'))
  .on('drain', () => console.log('背压释放'));

内置流实战


使用内置流实现静态web服务器
传统方式使用fs.readFile会导致高内存占用,流方案更优:

客户端请求

服务器

fs.createReadStream

设置HTTP头

客户端响应


细节如下:

客户端请求

ReadStream读取文件

是否Gzip压缩?

Gzip压缩流

直接响应

HTTP响应流

客户端接收

基础静态服务器实现:

const http = require('http');
const fs = require('fs');
const path = require('path');
 
http.createServer((req, res) => {
  const filePath = path.join(dirname, 'public', req.url);
  const readStream = fs.createReadStream(filePath);
  
  // 错误处理
  readStream.on('error', (err) => {
    res.statusCode = 404;
    res.end('File not found');
  });
 
  // 根据扩展名设置Content-Type 
  const ext = path.extname(filePath);
  const mimeTypes = {
    '.html': 'text/html',
    '.js': 'text/javascript',
    '.css': 'text/css',
    '.png': 'image/png'
  };
  
  res.setHeader('Content-Type', mimeTypes[ext] || 'text/plain');
  readStream.pipe(res); // 核心管道操作 
}).listen(3000);

带压缩的增强版本:

const zlib = require('zlib');
 
// 在基础版本上添加压缩支持 
http.createServer((req, res) => {
  // ...文件路径处理同上...
  
  const acceptEncoding = req.headers['accept-encoding'] || '';
  const readStream = fs.createReadStream(filePath);
  
  // 根据客户端支持的压缩方式选择处理器 
  if (acceptEncoding.includes('gzip')) {
    res.setHeader('Content-Encoding', 'gzip');
    readStream.pipe(zlib.createGzip()).pipe(res);
  } else if (acceptEncoding.includes('deflate')) {
    res.setHeader('Content-Encoding', 'deflate');
    readStream.pipe(zlib.createDeflate()).pipe(res);
  } else {
    readStream.pipe(res);
  }
  
  // 错误处理同上...
}).listen(3000);

总结

客户端请求

Read File Stream

Gzip Transform Stream

HTTP Response

客户端

流的错误处理


流错误处理至关重要,未处理的流错误会导致进程崩溃。

基础错误处理模式:

const stream = fs.createReadStream('largefile.txt');
 
// 必须添加error监听器
stream.on('error', (err) => {
  console.error('读取错误:', err.message);
  // 清理资源
});
 
stream.pipe(process.stdout);

管道链的错误处理:

const source = fs.createReadStream('input.txt');
const transform = zlib.createGzip();
const destination = fs.createWriteStream('output.gz');
 
source.on('error', handleError);
transform.on('error', handleError);
destination.on('error', handleError);
 
function handleError(err) {
  console.error('管道错误:', err);
  // 销毁所有相关流
  source.destroy();
  transform.destroy();
  destination.end();
}
 
source.pipe(transform).pipe(destination);

使用pipeline API(Node.js 10+推荐):

const { pipeline } = require('stream');
 
pipeline(
  fs.createReadStream('input.txt'),
  zlib.createGzip(),
  fs.createWriteStream('output.gz'),
  (err) => {
    if (err) {
      console.error('管道处理失败:', err);
    } else {
      console.log('管道处理成功');
    }
  }
);

再来看一个例子

const { pipeline } = require('stream');
 
pipeline(
  fs.createReadStream('input.txt'),
  zlib.createGzip(),
  fs.createWriteStream('output.gz'),
  (err) => { // 统一错误捕获 
    if (err) {
      console.error('Pipeline failed:', err);
      // 清理资源:删除损坏文件 
      fs.unlinkSync('output.gz');
    }
  }
);

最佳实践总结

  1. 始终处理错误:对流添加error事件监听器
  2. 利用管道:使用.pipe()或pipeline简化流连接
  3. 控制背压:监听drain事件管理写入速度
  4. 合理销毁资源:使用.destroy()手动终止流
  5. 选择对象模式:当处理非Buffer/String数据类型时

数据块

转换

输出

源流

处理流1

处理流2

目标流

错误处理器

流的核心哲学是分而治之:通过将大型操作分解为小块处理,实现内存高效、响应迅速的应用程序。
掌握流是成为Node.js专家的必经之路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值