我们今天来学习一下nodejs中的Stream相关知识,我们从以下几个例子开始讲起。
-
第一个例子,新建1.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左右的文件
-
什么是Steam流
-
释义 -
stream是水流,但默认没有水 -
stream.write可以让水流中有水(数据) -
每次写的小数据叫做chunk(块) -
产生数据的一段叫做source(源头) -
得到数据的一段叫做sink(水池)

-
第二个例子,没有使用数据流进行读写,新建2.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')
-
第三个例子,使用Stream改写第二个例子
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')
-
分析 -
内存占用会少很多

-
什么是管道(pipe)
-
释义 -
两个流可以用一个管道相连,stream1的末尾连接上stream2的开端,只要stream1有数据,就会流到stream2 -
常用代码
stream1.pipe(stream2)
// 链式操作
a.pipe(b).pipe(c)
// 等价于
a.pipe(b)
b.pipe(c)

-
管道可以通过事件实现,一般不这么写直接用pipe
// stream1 一有数据就塞给 stream2
stream1.on('data',(chunk)=>{
stream2.write(chunk)
})
// stream1停了,就停掉stream2
stream1.on('end',()=>s{
stream2.end()
})
-
Stream对象的原型、事件
-
新建4.js
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支持的事件和方法

stream.on('data', (chunk)=>{
console.log(chunk)
// chunk是一个buffer,就是每次把数据读成二进制的形式放在内存里,没有时间变成字符串,字符串需要编码,这里打出来就是十六进制表示01
console.log(chunk.toString()) //就可以变成字符串
})
-
这里特别注意下drain事件,因为面试常考,但是实际开发用的不多。可以参考下面的例子, -
简单理解,在处理大量数据或以较慢的速度写入数据的情况下,写入数据可能会超过可写流的缓冲区容量,导致暂时停止写入。drain事件的触发表示缓冲区已经排空,可以继续写入数据了。
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('数据写入完成');
-
Stream的分类
-
有四类 -
可读流 (Readable),提供从数据源读取数据的流。示例:文件读取流、HTTP 请求流。
const fs = require('fs');
const readableStream = fs.createReadStream('example.txt');
-
可写流 (Writable),提供向目标写入数据的流。示例:文件写入流、HTTP 响应流。
const fs = require('fs');
const writableStream = fs.createWriteStream('output.txt');
-
双工流(可读可写双向) (Duplex) ,同时具有读取和写入功能的流。示例:网络套接字、WebSocket。
const net = require('net');
const duplexStream = new net.Socket();
-
转换流(可读可写变化) (Transform),类似于双工流,但是可以修改或转换数据。示例:数据压缩、加密。
const zlib = require('zlib');
const transformStream = zlib.createGzip();
-
可读和可写流的特点
-
可读(Readable Stream) -
可读流有静止态paused和流动态flowing -
默认处于paused态 -
添加data事件监听,它就变为flowing态 -
删掉data事件监听,它就变为paused态 -
pause()可以将它变为paused -
resume()可以将它变为flowing -
可写(Writeable Stream) -
调用stream.end()之后 -
而且缓冲区数据都已经传给底层系统之后 -
触发finish事件 -
表示可以加点水了 -
调用stream.write(chunk)的时候,可能会得到false -
false的意思是你写太快了,数据积压了 -
这个时候我们就不能再write了,要监听drain -
等drain事件触发了,我们才能继续write -
drain流干了事件 -
finish事件
原文始发于微信公众号(前端之乐):深入学习Node.js中的Stream:从基础到实战
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/274572.html