什么是流
流是支持Node.js应用程序的基本概念之一。
它们是一种处理读/写文件、网络通信或任何类型的端到端信息交换的有效方式。
流不是Node.js特有的概念。它们在几十年前就被引入Unix操作系统,程序之间可以通过管道操作符(| )传递流进行交互。
例如,在传统的方式中,当你告诉程序读取一个文件时,文件被读入内存,从头到尾,然后你处理它。
使用流,你逐片读取它,处理它的内容,而不把它全部留在内存中。
Node.jsstream 模块提供了一个基础,所有的流媒体API都是在这个基础上建立的。
为什么是流
流基本上提供了使用其他数据处理方法的两个主要优势。
- 内存效率:你不需要在处理之前在内存中加载大量的数据
- 时间效率:一旦有了数据就开始处理,而不是等到整个数据载荷可用时才开始,这需要的时间要少得多。
一个流的例子
一个典型的例子是从磁盘中读取文件的例子。
使用Nodefs 模块,你可以读取一个文件,当一个新的连接建立在你的http服务器上时,通过HTTP提供服务。
const http = require('http')
const fs = require('fs')
const server = http.createServer(function (req, res) {
fs.readFile(__dirname + '/data.txt', (err, data) => {
res.end(data)
})
})
server.listen(3000)
readFile() 读取文件的全部内容,并在完成后调用回调函数。
res.end(data) 在回调函数中,将把文件内容返回给HTTP客户端。
如果文件很大,这个操作将花费相当多的时间。下面是用流写的同样的东西。
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const stream = fs.createReadStream(__dirname + '/data.txt')
stream.pipe(res)
})
server.listen(3000)
我们没有等到文件被完全读完,而是在有一大块数据准备发送的时候就开始把它流向HTTP客户端。
pipe()
上面的例子使用了stream.pipe(res) :在文件流上调用了pipe() 方法。
这段代码做了什么?它接收源文件,并将其输送到一个目的地。
你在源流上调用它,所以在这个例子中,文件流被管道到HTTP响应中。
pipe() 方法的返回值是目标流,这是一个非常方便的东西,让我们可以连锁调用多个pipe() ,像这样。
src.pipe(dest1).pipe(dest2)
这种结构与做
src.pipe(dest1)
dest1.pipe(dest2)
流驱动的Node APIs
由于它们的优势,许多Node.js核心模块提供了原生的流处理能力,最明显的是。
process.stdin返回一个连接到stdin的流process.stdout返回一个连接到stdout的流process.stderr返回一个连接到stderr的流fs.createReadStream()创建一个到文件的可读流fs.createWriteStream()创建一个到文件的可写流net.connect()启动一个基于流的连接http.request()返回http.ClientRequest类的一个实例,它是一个可写流zlib.createGzip()使用gzip(一种压缩算法)将数据压缩成一个流zlib.createGunzip()解压一个gzip流。zlib.createDeflate()使用deflate(一种压缩算法)压缩数据到一个流中zlib.createInflate()解压一个deflate流
不同类型的流
有四种类型的流。
Readable:你可以从一个流中管,但不能管到(你可以接收数据,但不能向它发送数据)。当你把数据推入一个可读流时,它被缓冲,直到消费者开始读取数据。Writable流:你可以用管道进入,但不能用管道流出(你可以发送数据,但不能从它接收)。Duplex可读流:一种既可以输入又可以输出的流,基本上是可读流和可写流的组合。TransformTransform流:Transform流与Duplex类似,但输出是其输入的一个转换。
如何创建一个可读流
我们从stream 模块中获得可读流,并初始化它。
const Stream = require('stream')
const readableStream = new Stream.Readable()
现在流被初始化了,我们可以向它发送数据。
readableStream.push('hi!')
readableStream.push('ho!')
如何创建一个可写的流
为了创建一个可写流,我们扩展了基础Writable 对象,并实现了它的_write()方法。
首先创建一个流对象。
const Stream = require('stream')
const writableStream = new Stream.Writable()
然后实现_write 。
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
现在你可以用管道将一个可读流引入。
process.stdin.pipe(writableStream)
如何从一个可读流中获取数据
我们如何从一个可读流中读取数据?使用一个可写流。
const Stream = require('stream')
const readableStream = new Stream.Readable()
const writableStream = new Stream.Writable()
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
readableStream.pipe(writableStream)
readableStream.push('hi!')
readableStream.push('ho!')
你也可以直接消费一个可读流,使用readable 事件。
readableStream.on('readable', () => {
console.log(readableStream.read())
})
如何发送数据到一个可写流
使用流write() 方法。
writableStream.write('hey!\n')
向一个可写流发出信号,表示你结束了写作
使用end() 方法。
const Stream = require('stream')
const readableStream = new Stream.Readable()
const writableStream = new Stream.Writable()
writableStream._write = (chunk, encoding, next) => {
console.log(chunk.toString())
next()
}
readableStream.pipe(writableStream)
readableStream.push('hi!')
readableStream.push('ho!')
writableStream.end()