Stream用英文定义流。流(Stream)是一个抽象的数据接口。Node.js中的许多对象都实现了流。Stream是一个EventEmitter对象。的一个实例,简单来说就是一个可以取数据(在Buffer中),或者说可以吸收数据的东西,其本质就是让数据流动起来。看一张图可能更直观:注:stream不是node.js特有的概念,而是一个操作系统最基本的操作方式,但是node.js有API支持这种操作方式。|linux命令的一部分是stream。为什么需要学习流视频播放示例?我的朋友一定在线看过电影。对比一下定义中的图——桶流水线图。source是服务器端的视频,dest是自己的播放器(或者浏览器里的flash和h5.video)。大家想一想,看电影的方式就像上图给管道换水一样。视频从服务器一点一点的推流到本地播放器,边推边播,推流后视频播放完毕。说明:在这个视频播放的例子中,如果我们不使用管道和流式传输,我们会直接从服务器加载视频文件然后播放。会因为内存占用过多而引起很多问题,导致系统死机或崩溃。因为我们的网速、内存、cpu运算速度有限,而且有多个程序共享使用。一个视频文件加载后可能有好几个g。很大。读取大文件数据的例子就有这样的需求。读取大文件数据的例子使用文件读取consthttp=require('http');constfs=require('fs');constpath=require('path');constserver=http.createServer(function(req,res){constfileName=path.resolve(__dirname,'data.txt');fs.readFile(fileName,function(err,数据){res.end(数据);});});server.listen(8000);使用文件读取这段代码的语法没有问题,但是如果data.txt文件很大,会达到几百M,响应大量用户并发请求时,程序可能会消耗大量内存,这可能会导致用户连接缓慢。而且,如果并发请求太大,服务器内存开销也会很大。这个时候我们再来看一下带stream的实现。consthttp=require('http');constfs=require('fs');constpath=require('路径');constserver=http.createServer(function(req,res){constfileName=path.resolve(__dirname,'data.txt');letstream=fs.createReadStream(fileName);//这行已经改了stream.pipe(res);//这行已经改变});服务器.listen(8000);使用Stream不需要读取所有的文件并返回,而是边读边返回,数据通过管道流向客户端,真正减轻了服务端的压力。看了两个例子,想必小伙伴们应该知道为什么要用stream了吧!因为一次性读取和操作大文件,内存和网络都受不了,所以让数据流一点点操作一下。stream循环的过程再看这张bucketpipeline流程图,可以看出stream的整个流动过程包括source,dest,以及连接两者的pipeline(stream的核心),介绍这三者指导大家了解流循环过程。流从何而来?soucrestream常见的源码方式有以下三种:在控制台的http请求中输入请求,读取文件。先说一下从控制台输入的方法。2、3两种方法的应用场景会在流应用场景讲解章节详细介绍。看一段process.stdin的代码process.stdin.on('data',function(chunk){console.log('streambystdin',chunk)console.log('streambystdin',chunk.toString())})//在控制台输入koalakoala后,通过stdin输出结果流被data事件监听,process.stdin是一个stream对象,data是stream对象用来监听传入数据的自定义函数。从输出中可以看出process.stdin是一个流对象。说明:流对象可以监听“data”、“end”、“opne”、“close”、“error”等事件。node.js中监听自定义事件使用.on方法,如process.stdin.on('data',…),req.on('data',…),这样可以直观的监听流数据的输入和结束连接到桶管道。从bucketpipeline流程图可以看出,source和dest之间有一条相连的pipeline管道。它的基本语法是source.pipe(dest),source和dest是通过管道连接,数据从source流向dest。stream到哪里去了-deststream常见的三种输出方式:在控制台HTTP请求中输出响应并写入文件stream应用场景stream的应用场景主要是处理IO操作,http请求和文件操作是IO操作。这里再提一下stream的本质——因为一次性的IO操作太大,硬件开销太大,影响软件的运行效率,所以IO分批、分段操作,让数据像水管一样流动,直到流完。即,操作完成。下面介绍几个常用的应用场景,介绍一个压力测试小工具ab,一个压力测试网络请求的工具。ab的全称是Apachebench,是Apache自带的工具。因此,必须安装Apache才能使用ab。macos系统自带Apache,windows用户可以根据自己的情况安装。在运行ab之前启动Apache。macos的启动方式是sudoapachectlstart。Apachebench对应参数的详细学习地址,有兴趣的可以看看Apachebench对应参数的详细学习地址。改进。stream的需求在get请求中应用:使用node.js实现一个http请求,读取data.txt文件,创建服务,监听8000端口,读取文件返回给客户端,使用a说get请求时的套路和文件读取对比,看下面的例子。经常使用文件读取返回客户端响应的例子,文件名为getTest1.js//getTest.jsconsthttp=require('http');constfs=require('fs');constpath=require('path');constserver=http.createServer(function(req,res){constmethod=req.method;//获取请求方法if(method==='GET'){//获取请求方法判断constfileName=path.resolve(__dirname,'data.txt');fs.readFile(fileName,function(err,data){res.end(data);});}});服务器.listen(8000);使用stream返回客户端终端响应部分修改以上代码,文件命名为getTest2.js//getTest2.js//主要展示变化的部分constserver=http.createServer(function(req,res){constmethod=req.method;//获取请求方法if(method==='GET'){//获取请求constfileName=path.resolve(__dirname,'data.txt');letstream=fs.createReadStream(fileName);stream.pipe(res);//使用res作为流的目标}});服务器.listen(8000);对于下面get请求中使用stream的例子,会有朋友质疑response是不是也是stream对象,没错,对于bucket流水线图来说,response就是一个dest。虽然在get请求中可以使用流,但是与直接读取文件相比,读取res.end(data)有什么好处呢?这个时候就用到了我们刚刚推荐的压测小工具。两段代码getTest1和getTest2,增加data.txt的内容,使用ab工具测试,运行命令ab-n100-c100http://localhost:8000/其中-n100表示??发送100个请求依次,-c100表示??一次发送的请求数为100。对比结果发现,使用stream后,性能有非常大的提升,大家自己看吧。贴子中使用stream请求微信小程序地址,通过贴子生成二维码。/**微信二维码生成接口*paramssrc微信url/其他图片请求链接*paramslocalFilePath:本地路径*paramsdata:微信请求参数**/constdownloadFile=async(src,localFilePath,data)=>{try{constws=fs.createWriteStream(localFilePath);returnnewPromise((resolve,reject)=>{ws.on('finish',()=>{resolve(localFilePath);});if(data){request({method:'POST',uri:src,json:true,body:data}).pipe(ws);}else{request(src).pipe(ws);}});}catch(e){logger.error('wxdownloadFile错误:',e);扔e;}}看这段使用stream的代码,为本地文件对应的路径创建一个stream对象,然后直接.pipe(ws)将post请求的数据流传输到这个本地文件中。这种流式应用在node后台开发过程中还是比较常用的。post和get使用stream总结一下request和response都是stream对象,可以利用stream的特性。两者的区别在于我们先看一下桶流水线流程图。request是source类型,就是图中的source,response是dest的类型,就是图中的destination。文件操作中使用流复制文件的例子constfs=require('fs')constpath=require('path')//两个文件名constfileName1=path.resolve(__dirname,'data.txt')constfileName2=path.resolve(__dirname,'data-bak.txt')//读取文件的流对象constreadStream=fs.createReadStream(fileName1)//写入文件的流对象constwriteStream=fs.createWriteStream(fileName2)//通过管道复制,数据流转readStream.pipe(writeStream)//数据读取完成并监听,即复制完成readStream.on('end',function(){console.log('copycompleted')})看完这段代码,要判断是否是副本似乎很简单。创建一个readStream数据流,一个可写数据流writeStream,然后直接通过pipe管道传输数据流。这种使用流的拷贝比读写存储文件的拷贝性能要高很多。因此,当你遇到文件操作的需求时,首先要评估是否需要使用stream来实现。一些前端打包工具的底层实现目前流行的一些前端打包构建工具都是用node.js写的。打包构建的过程必然是一个频繁的文件操作过程,离不开流。比如现在流行的gulp,有兴趣的朋友可以看看源码。流的类型ReadableStreamReadableStreamWriteableStreamWritableDataStreamDuplexStreamBidirectionalDataStream,可以同时读写TransformStream转换后的数据流,可读可写,可以转换(处理)数据(不常用)之前的文章都是关于前两种可读数据流和可写数据流的。第四个流不常用。如果你需要,你可以在网上搜索。接下来,我将解释第三种数据流,DuplexStream。DuplexStream是双向的,既可读又可写。双工流实现了Readable和Writable接口。双工流的例子有tcpsocketszlibstreamscryptostreams我没有在项目中使用过双工流,一些DuplexStream的内容可以参考这篇文章NodeJSStream双工流的缺点是什么使用rs.pipe(ws)来写文件是不行的将rs的内容追加到ws的后面,而是直接用rs的内容覆盖掉ws原来的内容。ended/closed流不能被重用,必须重新创建数据流。pipe方法返回目标数据流。比如a.pipe(b)返回的是b,所以在监听事件的时候,请注意监听的对象是否正确。写成:代码示例:data.on('end',function(){console.log('dataend');}).pipe(a).on('end',function(){console.log('aend');}).pipe(b).on('end',function(){console.log('bend');});stream的常用类库event-stream用于函数式编程感觉awesome-nodejs#streams也是一个不错的第三方流库。有兴趣的朋友可以看看github上的总结。看完这篇文章,你是不是对stream有了一定的了解,知道node对于文件处理还是有一些优势的?完美的解决方案。本文分三次展示了bucketpipeline的流程图。重要的事希望你记住三遍。除了上面的内容,大家还会不会有一些想法,比如stream数据流的具体内容是什么?二进制或者字符串类型或者其他类型,这种类型给stream带来什么好处?桶流水线图中的水管是什么时候触发的,即pipe函数?什么情况下会转发流?底层机制是什么?以上问题(由于文章篇幅分两篇)会在我的流的第二篇文章中详细讲解。今天就分享这么多,如果你对分享的内容感兴趣,可以关注公众号《程序员成长指南》,或者加入技术交流群,一起探讨。加入我们,一起学习!节点学习交流群交流群成员超过100人,无法自动入群。请加群助手微信号:[coder_qi]备注节点,会自动拉你入群。
