Node.js 流操作

Node.js 流操作

1. 概述

Node.js 中的流(Stream)是处理流式数据的核心机制,它允许应用程序以分块方式处理数据,而非一次性加载全部数据到内存。这种机制在处理大文件、网络I/O或实时数据流时尤为重要,能显著降低内存占用并提升性能。

2. 流的类型

Node.js 提供了四种基本的流类型,它们都是 EventEmitter 的实例,通过事件进行通信:

类型 特点 常见示例
可读流 (Readable) 数据来源的抽象,用于读取数据 fs.createReadStream, http.IncomingMessage
可写流 (Writable) 数据目的地点的抽象,用于写入数据 fs.createWriteStream, http.ServerResponse
双工流 (Duplex) 同时可读可写 net.Socket (TCP套接字)
转换流 (Transform) 特殊的双工流,可在读写过程中转换数据 zlib.createGzip(), zlib.createGunzip()

3. 流的基本操作

3.1 可读流操作

创建可读流

js 复制代码
// read.txt
我有一壶酒,足以慰风尘!
javascript 复制代码
const fs = require('fs')

// 可读流 readable
const readStream = fs.createReadStream('read.txt', {
  highWaterMark: 3 // 每次读取3个字节
})

readStream.on('data', chunk => {
  console.log(chunk.toString())
    /*
	  我
	  有
	  一
	  壶
	  酒
	  ,
	  足
	  以
	  慰
	  风
	  尘
	  !
	  */
})

readStream.on('end', () => {
  console.log('读取完毕')
})

readStream.on('error', err => {
  console.error('读取出错', err)
})

注意:

当可读流设置highWaterMark时,使用toString方法的时候,会出现截断数据的情况会出现乱码的情况,比如highWaterMark = 5,或者里面有英文的时候,会出现

"我�

�一�

��酒

,�

�以�

��风

尘�

�"

因为常用中文是三个字节,如果全中文不是3的倍数会出现截断中文的问题,或者英文只有一个字节,也会出现中文被截断,如果要tostring方法需要使用new StringDecoder().write() 方法

需要了解ASCII(0xxxxxxx) utf8格式 (110xxxxx 1110xxxx 1111xxxx -> 10xxxxxx )标准
🔍 背压问题:

  • readStream.on('data') 方法会出现背压问题,判断writable.write(chunk) === false
  • writeable可写流缓冲区清空,恢复可读流会触发 drain
  • highWaterMark 是软限制,不是硬上限。Node.js 允许略微超过,但一旦超过就会触发背压。
  • 即使底层还没真正"满",只要缓冲区接近阈值,就会返回 false 以提前控制流量。

3.2 可写流操作

创建可写流

javascript 复制代码
const fs = require('fs');
const writeStream = fs.createWriteStream('write.txt', {
  highWaterMark: 3 // 每次写入3个字节
})

writeStream.write('我有一壶酒,足以慰风尘!', 'utf8')
// 写入完成事件
writeStream.on('finish', () => {
  console.log('写入完成');
});
writeStream.end(() => {
  console.log('写入完毕')
})

4. 流的高级操作

4.1 管道操作 (Pipe)

管道是将一个可读流连接到一个可写流的机制,用于数据的传输。这是最常用的流操作方式。

文件复制示例

javascript 复制代码
const fs = require('fs')

const readStream = fs.createReadStream('read.txt', {
  highWaterMark: 3, // 每次读取3个字节
  encode: 'utf8'
})

const writeStream = fs.createWriteStream('write.txt', {
  highWaterMark: 6 // 每次写入6个字节
})

// 管道流
readStream.pipe(writeStream)

readStream.on('error', (err) => {
  console.error('读取出错', err)
})

writeStream.on('error', (err) => {
  console.error('写入出错', err)
})

readStream.on('data', (chunk) => {
  console.log('读取数据块:', chunk);
})

readStream.on('end', () => {
  console.log('读取结束');
})

writeStream.on('finish', () => {
  console.log('写入成功');
})

注意

ReadStream 默认highWaterMark为64KB

WriteStream 默认highWaterMark为16KB

系统读写速度是读的速度大于写的速度,

设置的时候highWaterMark注意一下

4.2 链式流 (Chaining)

链式流通过连接多个流来创建数据处理流水线,常用于压缩、解压缩等场景。

文件压缩示例

javascript 复制代码
const fs = require('fs');
const zlib = require('zlib');

// 读取文件并压缩后写入新文件
fs.createReadStream('large-file.txt')
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream('large-file.txt.gz'));

console.log('文件压缩中...');

4.3 转换流 (Transform)

转换流在数据流经时进行转换处理,常用于数据处理、压缩、加密等场景。

自定义转换流示例

javascript 复制代码
const fs = require('fs')
const Transform = require('stream').Transform

const readStream = fs.createReadStream('read.txt', {
  highWaterMark: 3, // 每次读取3个字节
  encode: 'utf8'
})

const transformStream = new Transform({
  transform(chunk, encoding, callback) {
    const transformedChunk = chunk.toString() + '|';// 每个数据块后面加上 '|'
    // 也就可以使用this.push 方法追加  
    // 如:
    // this.push(transformedChunk); 
    // callback()
    callback(null, transformedChunk);
  }
});
readStream.pipe(transformStream).pipe(process.stdout);
// 我|有|一|壶|酒|,|足|以|慰|风|尘|!|

5. 实际应用场景

5.1 大文件处理

使用流处理大文件,避免内存溢出:

javascript 复制代码
const fs = require('fs');

const readStream = fs.createReadStream('big-file.log');
const writeStream = fs.createWriteStream('processed.log');

// 过滤日志行
readStream.pipe(new Transform({
  transform(chunk, encoding, callback) {
    const lines = chunk.toString().split('\n');
    const filteredLines = lines.filter(line => line.includes('ERROR'));
    callback(null, filteredLines.join('\n') + '\n');
  }
}))
.pipe(writeStream);

writeStream.on('finish', () => {
  console.log('日志处理完成');
});

5.2 网络数据流处理

处理HTTP请求和响应的流式数据:

javascript 复制代码
const http = require('http');

http.createServer((req, res) => {
  // 从请求流中读取数据
  req.on('data', (chunk) => {
    console.log('接收数据:', chunk.toString());
  });
  
  // 处理完成后发送响应
  req.on('end', () => {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('请求已处理');
  });
}).listen(3000);

5.3 可读可写流Duplex

js 复制代码
const { Duplex } = require('stream');

class EchoDuplex extends Duplex {
  constructor(options) {
    super(options);
    this._buffer = []; // 用于暂存写入的数据
  }

  // 可写端:处理写入的数据
  _write(chunk, encoding, callback) {
    // 将写入的数据推入缓冲区,并触发可读端的读取
    this._buffer.push(chunk);
    // 模拟异步处理
    setImmediate(() => {
      // 将数据推送到可读端
      while (this._buffer.length > 0) {
        const data = this._buffer.shift();
        if (!this.push(data)) {
          // 如果 push 返回 false,说明可读端背压,暂停写入
          break;
        }
      }
      callback(); // 告知写入完成
    });
  }

  // 可读端:当消费者调用 read() 时触发
  _read(size) {
    // 此处无需主动推送,因为数据已在 _write 中通过 this.push() 推送
    // 如果有更多数据待发送,可在此继续 push
  }

  // 可选:处理 end()
  _final(callback) {
    // 所有写入完成后调用
    callback();
  }
}

// 使用示例
const echoStream = new EchoDuplex();

// 监听可读事件
echoStream.on('data', (chunk) => {
  console.log('Echo received:', chunk.toString());
});

// 写入数据
echoStream.write('Hello');
echoStream.write(' World!');
echoStream.end(); // 结束写入

6. 流操作最佳实践

  1. 始终处理错误事件:流操作中,错误处理是关键,避免程序崩溃
  2. 使用pipe()替代手动事件监听pipe()方法能自动处理数据传输,代码更简洁
  3. 注意背压处理:当写入速度慢于读取速度时,流会自动暂停,确保不会内存溢出
  4. 合理设置编码 :使用encoding: 'utf8'避免二进制数据处理问题
  5. 避免在data事件中进行复杂计算:可能导致数据处理不及时,影响性能

7. 总结

Node.js 流是处理大量数据的核心机制,通过四种基本类型(可读、可写、双工、转换)和管道操作,可以高效地处理文件、网络数据等场景。使用流能显著降低内存占用,提高应用程序的性能和响应能力,是构建高性能Node.js应用的必备技能。

掌握流操作后,开发者可以轻松处理GB级文件、实现高效的数据转换流水线,并构建响应迅速的网络应用。

相关推荐
Lupino3 小时前
Node.js 与 Haskell 混合网络编程踩坑记:TCP 粘包与状态不一致引发的“死锁”
javascript·node.js
神秘的猪头4 小时前
LangChain Tool 实战:让大模型“长出双手”,通过 Tool 调用连接真实世界
langchain·node.js·aigc
爱尚你19935 小时前
Redis6.2+ Stream 安全清理:避免内存爆炸的最佳实践
redis·stream
小北方城市网5 小时前
第 10 课:Node.js 后端企业级进阶 —— 任务管理系统后端优化与功能增强(续)
大数据·前端·vue.js·ai·性能优化·node.js
Cshaosun7 小时前
阿里云宝塔面板部署vue+nodejs项目并实现https访问操作流程
vue.js·阿里云·https·node.js·宝塔·文件下载
Mr_Wu20187 小时前
Corepack 实现 pnpm 版本自动管理
node.js
天远数科7 小时前
Node.js 全栈攻略:基于天远数据 API 开发即时身份核验中间件
大数据·node.js·编辑器·vim
Rabi'18 小时前
编译ATK源码
前端·webpack·node.js
萌萌哒草头将军19 小时前
AudioDock:服务器和 NAS 音频播放最棒的软件!🚀🚀🚀
服务器·docker·node.js