深入学习Node.js中的Stream:从基础到实战

我们今天来学习一下nodejs中的Stream相关知识,我们从以下几个例子开始讲起。

  1. 第一个例子,新建1.js
js 复制代码
const fs = require('fs')
const stream = fs.createWriteStream('./big_file.txt')
for (let i = 0; i < 1000000; i++) {
  stream.write(`这是第${i}行内容,我们需要很多很多内容,要不停地写文件啊啊啊啊啊啊回车\n`)
}
stream.end()//别忘了关掉
console.log('done')
  • 通过运行上面的这段代码,我们能得到一个10000行的文件
  • 分析
    • 我们通过打开流,多次往里面塞内容,关闭流
    • 最终我们得到一个1mb左右的文件
  1. 什么是Steam流
  • 释义
    • stream是水流,但默认没有水
    • stream.write可以让水流中有水(数据)
    • 每次写的小数据叫做chunk(块)
    • 产生数据的一段叫做source(源头)
    • 得到数据的一段叫做sink(水池)
  1. 第二个例子,没有使用数据流进行读写,新建2.js
js 复制代码
const http = require("http")
const fs = require("fs")
const server = http.createServer()
server.on('request', (request, response) => {
  fs.readFile('./big_file.txt', (error, data) => {
    if (error) throw error;
    response.end(data)
    console.log('done')
  })
})
server.listen(8888)
console.log('8888')
  • 直接用浏览器或者chrome访问
  • 分析
    • 用任务管理器看看Node.js内存占用,会很大概100mb,这就是不用流的问题

    • 访问前内存

    • 访问后开始读取文件的内存

  1. 第三个例子,使用Stream改写第二个例子
js 复制代码
const http = require("http")
const fs = require("fs")
const server = http.createServer()
server.on('request', (request, response) => {
  const stream = fs.createReadStream('./big_file.txt')
  stream.pipe(response)
  stream.on('end',()=>console.log('done'))
})
server.listen(8888)
console.log('8888')
  • 分析
    • 内存占用会少很多
  1. 什么是管道(pipe)
  • 释义
    • 两个流可以用一个管道相连,stream1的末尾连接上stream2的开端,只要stream1有数据,就会流到stream2
    • 常用代码
js 复制代码
stream1.pipe(stream2)
// 链式操作
a.pipe(b).pipe(c)
// 等价于
a.pipe(b)
b.pipe(c)
  • 管道可以通过事件实现,一般不这么写直接用pipe
js 复制代码
// stream1 一有数据就塞给 stream2
stream1.on('data',(chunk)=>{
  stream2.write(chunk)
})
// stream1停了,就停掉stream2
stream1.on('end',()=>s{
  stream2.end()
})
  1. Stream对象的原型、事件
  • 新建4.js
ini 复制代码
const fs = require('fs')
const s = fs.createReadStream('./big_file.txt')
console.log(s)
  • 运行node --inspect-brk 4.js
  • 打开浏览器进入调试模式,打上断点
  • 可以在控制台看到steam的层级结构,也就是Stream对象的原型链
    • 自身属性(由fs.ReadStream构造)
    • 原型:stream.Readable.prototype
    • 二级原型:stream.Stream.prototype
    • 三级原型:events.EventEmitter.prototype
    • 四级原型:Object.prototype
  • Stream对象都继承了EventEmitter
  • Stream支持的事件和方法
js 复制代码
stream.on('data', (chunk)=>{
  console.log(chunk)
  // chunk是一个buffer,就是每次把数据读成二进制的形式放在内存里,没有时间变成字符串,字符串需要编码,这里打出来就是十六进制表示01
  console.log(chunk.toString()) //就可以变成字符串
})
  • 这里特别注意下drain事件,因为面试常考,但是实际开发用的不多。可以参考下面的例子,
  • 简单理解,在处理大量数据或以较慢的速度写入数据的情况下,写入数据可能会超过可写流的缓冲区容量,导致暂时停止写入。drain事件的触发表示缓冲区已经排空,可以继续写入数据了。
js 复制代码
const fs = require('fs');
const writableStream = fs.createWriteStream('example.txt');

// 假设这是一个大型数据流
const data = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';

// 当写入的数据量超过缓冲区大小时,write 方法会返回 false
while (!writableStream.write(data)) {
  console.log('缓冲区已满,等待 drain 事件');
  // 在 drain 事件发生后继续写入
}

// 当缓冲区排空时,会触发 drain 事件
writableStream.on('drain', () => {
  console.log('缓冲区已排空,可以继续写入');
});

console.log('数据写入完成');
  1. Stream的分类
  • 有四类
    • 可读流 (Readable),提供从数据源读取数据的流。示例:文件读取流、HTTP 请求流。
js 复制代码
const fs = require('fs');
const readableStream = fs.createReadStream('example.txt');
  • 可写流 (Writable),提供向目标写入数据的流。示例:文件写入流、HTTP 响应流。
js 复制代码
const fs = require('fs');
const writableStream = fs.createWriteStream('output.txt');
  • 双工流(可读可写双向) (Duplex) ,同时具有读取和写入功能的流。示例:网络套接字、WebSocket。
js 复制代码
const net = require('net');
const duplexStream = new net.Socket();
  • 转换流(可读可写变化) (Transform),类似于双工流,但是可以修改或转换数据。示例:数据压缩、加密。
js 复制代码
const zlib = require('zlib');
const transformStream = zlib.createGzip();
  1. 可读和可写流的特点
  • 可读(Readable Stream)
    • 可读流有静止态paused和流动态flowing
    • 默认处于paused态
    • 添加data事件监听,它就变为flowing态
    • 删掉data事件监听,它就变为paused态
    • pause()可以将它变为paused
    • resume()可以将它变为flowing
  • 可写(Writeable Stream)
    • drain流干了事件
      • 表示可以加点水了
      • 调用stream.write(chunk)的时候,可能会得到false
      • false的意思是你写太快了,数据积压了
      • 这个时候我们就不能再write了,要监听drain
      • 等drain事件触发了,我们才能继续write
    • finish事件
      • 调用stream.end()之后
      • 而且缓冲区数据都已经传给底层系统之后
      • 触发finish事件
相关推荐
迷雾漫步者38 分钟前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-1 小时前
验证码机制
前端·后端
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235243 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240254 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar4 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人5 小时前
前端知识补充—CSS
前端·css
GISer_Jing5 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试