stream的基本概念和常用API概述
让数据流动起来。数据从原来的source流向dest,要像水一样,慢慢的一点一点通过一个管道流过去。stream并不是node.js独有的概念,而是一个操作系统最基本的操作方式,只不过node.js有API支持这种操作方式。linux命令的|就是stream,因此所有server端语言都应该实现stream的API。
为何要使用stream
例子,在线播放视频。一点一点从服务端将视频流动到本地播放器,一边流动一边播放,直到播放完成。
var http = require('http')
var fs = require('fs')
var path = require('path')
var server = http.createServer(function(req, res) {
var fileName = path.resolve(__dirname, 'data.txt')
fs.readFile(fileName, function(err, data) {
res.end(data)
})
})
server.listen(8000)这段node.js代码跑起来会读取文件,语法上没问题,但如果data.txt文件非常大,在响应大量用户的并发请求时,程序可能会消耗大量的内存,这样很可能会造成用户连接缓慢的问题。而且,如果并发请求过大,服务器内存开销也很大。
要解决该问题很简单,用stream改造一下。即不是把全部文件读取了再返回,而是一边读取一边返回,一点点地把数据流动到客户端。
var fs = require('fs')
var http = require('http')
var path = require('path')
var server = http.createServer(function(req, res) {
var fileName = path.resolve(__dirname, 'data.txt')
var stream = fs.createReadStream(fileName)
stream.pipe(res)
})
server.listen(8000)小结一下,之所以用stream,是因为一次性读取、操作大文件,内存和网络是吃不消的,因此要让数据流动起来,一点一点地进行操作。这符合分而治之的思想。
stream流转的过程
从管道换水的例子可看出,stream包括source, dest还有中间的管道,下面将通过这三方面介绍stream的过程。其中比较关键的api有:
- data事件,用来监听stream数据的输入
- end事件,用来监听stream数据输入完成
- fs.createReadStream方法,返回一个文件读取的stream对象
- fs.createWriteStream方法, 返回一个文件读取的stream对象
- pipe方法,用来做数据流转
source —— 从哪里来
stream常见的来源主要有三种:
- 从控制台输入
- http请求中的request
- 读取文件
运行如下代码,然后从控制台输入任何内容,都会被data事件监听到,process.stdin就是一个stream对象。注意data就是stream用来监听数据传入的一个自定义函数,后续会大量用到该方法。
process.stdin.on('data', function(chunk) {
console.log('stream by stdin', chunk.toString())
})http请求中的request输入可以参考如下代码片段。即客户端发起http请求,服务端可以通过这种方式(用到了data事件监听)。这种http请求一般是一个post请求,上传数据。注意,end用来监听stream数据传输完毕,一般和data共用。
req.on('data', function(chunk) {
// 一点一点 接受内容
data += chunk.toString()
})
res.on('end', function() {
// end表示数据接受完成
})读取文件用 fs.createReadStream(...) 可以返回一个读取文件的stream对象,该对象可以监听data和end事件
var fs = require('fs')
var readStream = fs.createReadStream('./file1.txt')
var length = 0
readStream.on('data', function(chunk) {
length += chunk.toString().length
})
readStream.on('end', function() {
console.log(length)
})管道
以上source三种代码示例有一个共同特点,就是对stream对象可以监听data和end事件。nodejs监听自定义事件要使用.on方法,例如 process.stdin.on('data', ...) ,能很直观地监听到stream数据的传入和结束。
dest —— 到哪里去
stream常见的输出方式主要有三种:
- 输出到控制台
- http请求中的response
- 写入文件
如果让控制台输入这个source直接通过管道连接到控制台输入,即让数据从输入直接流向输出,代码如下:
process.stdin.pipe(process.stdout)nodejs处理http请求时会用到req和res,其实这两者都是stream对象。其中,req是source,res是dest。所以stream方式读取文件然后直接返回http请求
var stream = fs.createReadStream(fileName)
stream.pipe(res)读取文件可以用stream,写入文件也可以用stream,其中 fs.createWriteStream(...) 会返回一个写入文件的stream对象,即dest。这段代码,就是将一个文件中的内容,一点一点地流动到另外的文件中,完成复制功能。
var fs = require('fs')
var readStream = fs.createReadStream('./file1.txt')
var writeStrea = fs.createWriteStream('./file2.txt')
readStream.pipe(writeStream)stream常见使用场景
stream常见的使用场景是http请求和文件操作。 总结来看,http请求和文件操作都属于IO,即stream主要的应用场景是处理IO,这又回到了stream的本质——由于一次性IO操作过大,硬件开销太多,影响软件运行效率,因此将IO分批分段操作,让数据一点一点地流动起来,直到操作完成。
小结
本章主要介绍了stream的基本概念和常用API
- stream的基本概念,即 source -> 管道 -> dest
- 为何要用stream —— 一次性操作IO,内存和网络开销过大
- source pipe dest各部分常用API
- stream的常见应用场景——IO操作
