第一个Stream例子

  1. 创建一个js文件: ``typescript const fs = require('fs') const stream = fs.createWriteStream('big_file.txt') //实际上可以理解为 这个stream可以往这个bigfile文件里面写东西 for(let i=0; i<100000; i++){ stream.write(这是第${i}行内容,我们需要很多很多内容,要不停地写文件aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 回车\n`) }

stream.end() //别忘了关掉stream console.log(‘done’)

  1. 2. 运行该文件
  2. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/851574/1606748934135-ad8bfd3e-d0b4-4e50-9fd8-56b6bdab5815.png#align=left&display=inline&height=46&margin=%5Bobject%20Object%5D&name=image.png&originHeight=46&originWidth=310&size=2314&status=done&style=none&width=310)<br />会创建一个big_file.txt (大小:100多MB)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/851574/1606748944354-088a65a4-e87e-4ea6-99f9-45f94a363dd8.png#align=left&display=inline&height=497&margin=%5Bobject%20Object%5D&name=image.png&originHeight=497&originWidth=1035&size=92445&status=done&style=none&width=1035)
  3. <a name="wSKiG"></a>
  4. ## 分析
  5. - 打开流,多次往里面塞内容, 关闭流
  6. 每次塞是不会覆盖之前的内容的,每次都是分开的
  7. - 看起来就是可以多次而已,没什么特别
  8. - 但是最终得到一个100MB的文件
  9. <a name="g7oDy"></a>
  10. # Stream - 流
  11. <a name="wWfPp"></a>
  12. ## 图解:
  13. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/851574/1606750077588-63b2411d-d33a-4791-98fa-675836d26cef.png#align=left&display=inline&height=530&margin=%5Bobject%20Object%5D&name=image.png&originHeight=530&originWidth=345&size=62219&status=done&style=none&width=345)
  14. <a name="Z7Gbo"></a>
  15. ## 释义:
  16. 结合上面的代码看
  17. - stream是水流,但默认没有水
  18. - stream.write 可以让水流中有水(数据)
  19. - 每次写的小数据叫做chunk(块)
  20. - 产生数据的一段叫做source(源头)
  21. - 得到数据的一段叫做sink(水池)
  22. chunk 数据块<br />为什么不叫data呢<br />data一般表示完整的数据,chunk只是一块数据
  23. <a name="wG1RJ"></a>
  24. # 第二个例子
  25. 1. 创建2.js文件
  26. ```typescript
  27. const fs = require('fs')
  28. const http = require('http')
  29. const server = http.createServer()
  30. server.on('request',(request,response)=>{
  31. //读上面创建的big_file.txt,放到response里面
  32. fs.readFile('./big_file.txt',(error,data)=>{
  33. if(error) throw error
  34. response.end(data)
  35. console.log('done')
  36. })
  37. })
  38. server.listen(8888)
  39. console.log(`8888`)
  1. 运行2.js

image.png

  1. 访问8888端口

image.png
发现网页正在不断加载,进度条在不断的拉长

分析

  • 用任务管理器看看 Node.js 内存占用,大概130Mb

big_file 120Mb + node.js本身8Mb

  • 内存占用太多了,这就是不用stream的缺点

一个用户的请求就用了100mb内存,那么10个用户,100个用户,服务器很难撑得住

  • 用steam就可以解决这个问题,用stream改写这个例子

第三个例子

用stream改写第二个例子

  1. const fs = require('fs')
  2. const http = require('http')
  3. const server = http.createServer()
  4. server.on('request',(request,response)=>{
  5. // 用流的形式去读bigFile
  6. fs.createReadStream('./big_file.txt')
  7. // 通过管道pipe将读完的bigFile传给response
  8. stream.pipe(response)
  9. stream.on('end', ()=>console.log('done'))
  10. })
  11. server.listen(8888)
  12. console.log(`8888`)

运行该文件后,可以使用 $ curl http://localhost:8888 去请求这个bigFile
一边请求一边打开任务管理器看看占用的内存有多少
image.png
一般会稳定到30多MB,直到传完

分析

跟第二个例子对比:
节省了很多的内存,而且时间并不会非常长

  • 查看node.js内存占用,基本不会高于40Mb
  • 文件stream 和 response stream 通过管道相连
    • 意思是 stream是一个流,response也是一个流,他们通过pipe管道连起来

image.png

  • stream1 相当于 读文件的stream
  • stream2 相当于 http发回给用户的response stream
  • 1和2本来是没什么关系的,但是我们用了一个管道 把他们接起来了。那么stream1的数据都会自动地流向stream2。

释义

两个流可以用一个管道相连
stream1的末尾连接上stream2的开端
只要stream1有数据,就会流到stream2

常用代码

stream1.pipe(stream2)

链式操作

  1. a.pipe(b).pipe(c)
  2. //等价于
  3. a.pipe(b)
  4. b.pipe(c)

管道

管道可以通过事件实现

  1. //stream1一有数据就塞给stream2
  2. stream1.on('data',(chunk)=>{
  3. stream2.write(chunk)
  4. })
  5. //stream1停了,就停掉stream2
  6. stream1.on('end',()=>{
  7. stream2.end()
  8. })
  1. 监听stream1的on data事件,一旦触发,就把chunk数据写到stream2上
  2. 监听stream1的on end事件,stream1 end, stream2 也end

一般不这样写,因为写这么多代码,还不如写一个pipe

小结

  • stream可以使你的内存降得非常低,从100多mb降到30多mb

比如说你有一个3g的文件,nodejs对于内存的大小使用是有限制的,3g的文件根本读不到内存里的,但是如果使用流,可以一点一点地读。当然你也可以控制每次读多少

  • 管道

可以将两个流连起来,连起来之后就可以实现数据在不同的地方的转换
比如上面的例子:从文件转换到网络