流的英文stream
,流(Stream)是一个抽象的数据接口,Node.js
中不少对象都实现了流,流是EventEmitter
对象的一个实例,总之它是会冒数据(以 Buffer
为单位),或者可以吸取数据的东西,它的本质就是让数据流动起来。 可能看一张图会更直观:javascript
注意:stream
不是node.js独有的概念,而是一个操做系统最基本的操做方式,只不过node.js有API支持这种操做方式。linux命令的|就是stream
。html
做者简介:koala,专一完整的 Node.js 技术栈分享,从 JavaScript 到 Node.js,再到后端数据库,祝您成为优秀的高级 Node.js 工程师。【程序员成长指北】做者,Github 博客开源项目 github.com/koala-codin…前端
小伙伴们确定都在线看过电影,对比定义中的图-水桶管道流转图
,source
就是服务器端的视频,dest
就是你本身的播放器(或者浏览器中的flash和h5 video)。你们想一下,看电影的方式就如同上面的图管道换水同样,一点点从服务端将视频流动到本地播放器,一边流动一边播放,最后流动完了也就播放完了。java
说明:视频播放的这个例子,若是咱们不使用管道和流动的方式,直接先从服务端加载完视频文件,而后再播放。会形成不少问题node
有一个这样的需求,想要读取大文件data的例子linux
使用文件读取git
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer(function (req, res) {
const fileName = path.resolve(__dirname, 'data.txt');
fs.readFile(fileName, function (err, data) {
res.end(data);
});
});
server.listen(8000);
复制代码
使用文件读取这段代码语法上并无什么问题,可是若是data.txt文件很是大的话,到了几百M,在响应大量用户并发请求的时候,程序可能会消耗大量的内存,这样可能形成用户链接缓慢的问题。并且并发请求过大的话,服务器内存开销也会很大。这时候咱们来看一下用stream
实现。程序员
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer(function (req, res) {
const fileName = path.resolve(__dirname, 'data.txt');
let stream = fs.createReadStream(fileName); // 这一行有改动
stream.pipe(res); // 这一行有改动
});
server.listen(8000);
复制代码
使用stream就能够不须要把文件所有读取了再返回,而是一边读取一边返回,数据经过管道流动给客户端,真的减轻了服务器的压力。github
看了两个例子我想小伙伴们应该知道为什么要使用stream
了吧!由于一次性读取,操做大文件,内存和网络是吃不消的,所以要让数据流动起来,一点点的进行操做。数据库
再次看这张水桶管道流转图
stream
整个流转过程包括source,dest,还有链接两者的管道pipe(stream的核心),分别介绍三者来带领你们搞懂stream流转过程。
stream
的常见来源方式有三种:
http
请求中的request
这里先说一下从控制台输入
这种方式,2和3两种方式stream应用场景
章节会有详细的讲解。
看一段process.stdin
的代码
process.stdin.on('data', function (chunk) {
console.log('stream by stdin', chunk)
console.log('stream by stdin', chunk.toString())
})
//控制台输入koalakoala后输出结果
stream by stdin <Buffer 6b 6f 61 6c 61 6b 6f 61 6c 61 0a>
stream by stdin koalakoala
复制代码
运行上面代码:而后从控制台输入任何内容都会被data
事件监听到,process.stdin
就是一个stream
对象,data 是stream
对象用来监听数据传入的一个自定义函数,经过输出结果可看出process.stdin
是一个stream对象。
说明: stream
对象能够监听"data"
,"end"
,"opne"
,"close"
,"error"
等事件。node.js
中监听自定义事件使用.on
方法,例如process.stdin.on(‘data’,…)
, req.on(‘data’,…)
,经过这种方式,能很直观的监听到stream
数据的传入和结束
从水桶管道流转图中能够看到,在source
和dest
之间有一个链接的管道pipe
,它的基本语法是source.pipe(dest)
,source
和dest
就是经过pipe链接,让数据从source
流向了dest
。
stream的常见输出方式有三种:
http
请求中的response
stream
的应用场景主要就是处理IO
操做,而http请求
和文件操做
都属于IO
操做。这里再提一下stream
的本质——因为一次性IO
操做过大,硬件开销太多,影响软件运行效率,所以将IO
分批分段进行操做,让数据像水管同样流动起来,直到流动完成,也就是操做完成。下面对几个经常使用的应用场景分别进行介绍
一个对网络请求作压力测试的工具ab
,ab
全称 Apache bench
,是 Apache
自带的一个工具,所以使用 ab
必需要安装 Apache
。mac os 系统自带 Apache
,windows
用户视本身的状况进行安装。运行ab
以前先启动 Apache
,mac os
启动方式是 sudo apachectl start
。
Apache bench对应参数的详细学习地址,有兴趣的能够看一下 Apache bench对应参数的详细学习地址
介绍这个小工具的目的是对下面几个场景能够进行直观的测试,看出使用stream带来了哪些性能的提高。
这样一个需求:
使用node.js实现一个http请求,读取data.txt文件,建立一个服务,监听8000端口,读取文件后返回给客户端,讲get请求的时候用一个常规文件读取与其作对比,请看下面的例子。
getTest1.js
// getTest.js
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer(function (req, res) {
const method = req.method; // 获取请求方法
if (method === 'GET') { // get 请求方法判断
const fileName = path.resolve(__dirname, 'data.txt');
fs.readFile(fileName, function (err, data) {
res.end(data);
});
}
});
server.listen(8000);
复制代码
getTest2.js
// getTest2.js
// 主要展现改动的部分
const server = http.createServer(function (req, res) {
const method = req.method; // 获取请求方法
if (method === 'GET') { // get 请求
const fileName = path.resolve(__dirname, 'data.txt');
let stream = fs.createReadStream(fileName);
stream.pipe(res); // 将 res 做为 stream 的 dest
}
});
server.listen(8000);
复制代码
对于下面get请求中使用stream的例子,会不会有些小伙伴提出质疑,难道response也是一个stream对象,是的没错,对于那张水桶管道流转图
,response就是一个dest。
虽然get请求中可使用stream,可是相比直接file文件读取·res.end(data)
有什么好处呢?这时候咱们刚才推荐的压力测试小工具就用到了。getTest1
和getTest2
两段代码,将data.txt
内容增长大一些,使用ab
工具进行测试,运行命令ab -n 100 -c 100 http://localhost:8000/
,其中-n 100
表示前后发送100次请求,-c 100
表示一次性发送的请求数目为100个。对比结果分析使用stream后,有很是大的性能提高,小伙伴们能够本身实际操做看一下。
一个经过post请求微信小程序的地址生成二维码的需求。
/* * 微信生成二维码接口 * params src 微信url / 其余图片请求连接 * params localFilePath: 本地路径 * params data: 微信请求参数 * */
const downloadFile=async (src, localFilePath, data)=> {
try{
const ws = fs.createWriteStream(localFilePath);
return new Promise((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 error: ',e);
throw e;
}
}
复制代码
看这段使用了stream的代码,为本地文件对应的路径建立一个stream对象,而后直接.pipe(ws)
,将post请求的数据流转到这个本地文件中,这种stream的应用在node后端开发过程当中仍是比较经常使用的。
request和reponse同样,都是stream对象,可使用stream的特性,两者的区别在于,咱们再看一下水桶管道流转图
,
一个文件拷贝的例子
const fs = require('fs')
const path = require('path')
// 两个文件名
const fileName1 = path.resolve(__dirname, 'data.txt')
const fileName2 = path.resolve(__dirname, 'data-bak.txt')
// 读取文件的 stream 对象
const readStream = fs.createReadStream(fileName1)
// 写入文件的 stream 对象
const writeStream = fs.createWriteStream(fileName2)
// 经过 pipe执行拷贝,数据流转
readStream.pipe(writeStream)
// 数据读取完成监听,即拷贝完成
readStream.on('end', function () {
console.log('拷贝完成')
})
复制代码
看了这段代码,发现是否是拷贝好像很简单,建立一个可读数据流readStream
,一个可写数据流writeStream
,而后直接经过pipe
管道把数据流转过去。这种使用stream的拷贝相比存文件的读写实现拷贝,性能要增长不少,因此小伙伴们在遇到文件操做的需求的时候,尽可能先评估一下是否须要使用stream
实现。
目前一些比较火的前端打包构建工具
,都是经过node.js
编写的,打包和构建的过程确定是文件频繁操做的过程,离不来stream
,例如如今比较火的gulp
,有兴趣的小伙伴能够去看一下源码。
Readable Stream
可读数据流Writeable Stream
可写数据流Duplex Stream
双向数据流,能够同时读和写Transform Stream
转换数据流,可读可写,同时能够转换(处理)数据(不经常使用)以前的文章都是围绕前两种可读数据流和可写数据流,第四种流不太经常使用,须要的小伙伴网上搜索一下,接下来对第三种数据流Duplex Stream 说明一下。
Duplex Stream
双向的,既可读,又可写。 Duplex streams
同时实现了 Readable
和Writable
接口。 Duplex streams
的例子包括
tcp sockets
zlib streams
crypto streams
我在项目中还未使用过双工流,一些Duplex Stream的内容能够参考这篇文章NodeJS Stream 双工流rs.pipe(ws)
的方式来写文件并非把 rs 的内容 append
到 ws 后面,而是直接用 rs 的内容覆盖 ws 原有的内容pipe
方法返回的是目标数据流,如 a.pipe(b)
返回的是 b,所以监听事件的时候请注意你监听的对象是否正确pipe
方法来串联数据流的话,你就要写成: 代码实例:data
.on('end', function() {
console.log('data end');
})
.pipe(a)
.on('end', function() {
console.log('a end');
})
.pipe(b)
.on('end', function() {
console.log('b end');
});
复制代码
event-stream 用起来有函数式编程的感受
awesome-nodejs#streams也是一个不错的第三方stream库,有兴趣的小伙伴能够github看一下
看完了这篇文章是否是对stream有了必定的了解,而且知道了node对于文件处理仍是有完美的解决方案的。本文中三次展现了水桶管道流转图
,总要的事情说三遍但愿小伙伴们记住它,除了以上内容小伙伴们会不会有一些思考,好比
string
类型仍是其余类型,该类型为stream带来了什么好处?pipe
函数何时触发的呢?在什么状况下触流转发?底层机制是什么? 上面的疑问(因为篇幅过长拆分为两篇)会在我stream
的第二篇文章为你们详细讲解。今天就分享这么多,若是对分享的内容感兴趣,能够关注公众号「程序员成长指北」,或者加入技术交流群,你们一块儿讨论。
加入咱们一块儿学习吧!
node学习交流群
交流群满100人不能自动进群, 请添加群助手微信号:【coder_qi】备注node,自动拉你入群。