深入学习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事件
相关推荐
excel2 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子8 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构15 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep16 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss20 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风21 分钟前
html二次作业
前端·html
江城开朗的豌豆25 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵25 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae
画月的亮28 分钟前
前端处理导出PDF。Vue导出pdf
前端·vue.js·pdf
江城开朗的豌豆34 分钟前
拆解Redux:从零手写一个状态管理器,彻底搞懂它的魔法!
前端·javascript·react.js