03-流(Stream)和管道(Pipe)
6.2 流(Stream)和管道(Pipe)
流技术贯穿了整个Node核心,为HTTP和其他形式的网络请求提供支持。同时它也为文件系统提供了支持,这也是为什么我在开始深入讲解文件系统模块之前先介绍它。
流对象是一个抽象接口,也就是说你不会直接去创建流,而是跟实现流的对象打交道,比如HTTP请求、文件系统的读写流、ZLib压缩或者 process.stdout
。唯一能真正实现流API的方法是创建一个自定义流。由于这已经超出了本书的范围,所以我会把这部分内容放在后续的练习中。现在,我们来看看其他功能是如何使用流的。
因为Node中有太多对象实现了流接口,所以Node中所有的流都具备以下几个基本功能:
- 你可以通过
setEncoding
来改变流数据的编码; - 你可以检查流是否可读、是否可写,还是两者都可以;
- 你可以捕获流事件,比如接收数据或关闭连接等事件,并给这些事件分别设置回调函数;
- 你可以暂停和恢复流;
- 你可以把一个可读流中的数据通过管道传入一个可写流。
注意那些跟流的读写有关的部分。既需要读也需要写的流称为双工流( duplex )。当输入和输出有因果关联时,双工流也被称为变换(transform)。在本章后面介绍ZLib压缩的章节中,我们会看到这种类型的流。
可读流在创建之后会处于暂停状态,也就是说在显式调用读取的接口( stream. read ()
)或者恢复接口( stream.resume()
)之前,它不会读取任何数据。然而,一旦我们触发数据事件(获取可读流中数据的方式),那么我们使用的流实现(如文件系统的可读流)将切换到流动模式。在流动模式下,数据可以随时被访问并被发送到应用程序。
可读流支持多个事件,但在大多数可读流的应用中,我们只对3个事件感兴趣:数据(data)事件、结束(end)事件和错误(error)事件。当有一个新数据块可用时, data
事件会被发送,当所有数据都被处理后 end
事件会被发送。当然,发生错误时 error
事件会被发送。我们在第5章的例5-1中看到了相关的可读流,我们稍后将在文件系统中再次看到它。
可写流是数据即将被发送(写入)的目的地。我们所需要侦听的事件包括 error
。当 end()
方法被调用、且所有的数据被写入之后,我们需要监听 error
事件和 finish
事件。当尝试写入数据却返回 false
时,还需要监听 drain
事件。在第5章的例5-2中创建一个HTTP客户端时,我们使用了一个可写流,我们还会在第6.3节中看到它与文件系统模块一起使用。
双工流同时具有可读流和可写流的功能。转换流是一种双工流,不同之处在于,与内部输入和输出缓冲器彼此独立存在的双工流不同,转换流直接连接两个流,并在中间加上转换数据的步骤。在代码底层,转换流必须实现一个函数 _transform ()
,它接收传入的数据,对数据执行一些操作,然后将操作后的数据输出。
为了更好地理解转换流,我们要仔细了解所有流都具有的功能: pipe()
函数。在例5-1中我们已经见过它了,一个可写流将文件内容直接通过管道传给HTTP响应对象:
// create and pipe readable stream
var file = fs.createReadStream(pathname);
file.on("open", function() {
file.pipe(res);
});
在这里, pipe()
将文件中的内容(流)拿出来,然后输出到 http.ServerResponse
对象中。在Node文档中,我们知道这个对象实现了可写流的接口,稍后我们会看到 fs.createReadStream()
返回一个 fs.ReadStream
,它是一个可读流的实现。可读流支持的方法之一就是 pipe()
到一个可写流。
后面我们将会详细了解一个使用Zlib模块来压缩文件的例子,不过现在可以先看一眼。下面是一个绝佳的转换流示例。
var gzip = zlib.createGzip();
var fs = require('fs');
var inp = fs.createReadStream('input.txt');
var out = fs.createWriteStream('input.txt.gz');
inp.pipe(gzip).pipe(out);
输入是一个可读流,输出是一个可写流。通过管道将一个流的内容传递给另一个,不过要先经过压缩,这就是转换。