Skip to content

本章概要:

  • 什么是流及其如何使用
  • 如何使用Node集成的流API
  • 流API在Node0.8版本下的使用
  • 0.10版本以后流的原始类
  • 测试流的策略

流是基于事件的API,用于管理和处理数据,而且有不错的效率。借助事件和非阻塞I/O库,流模块允许在其可用的时候动态处理,在其不需要的时候释放掉。

流的核心模块为构建基于事件的流类提供抽象工具。这就意味着可以使用模块实现流,而不需要自己实现。但是为了最大限度地利用流,理解它的工作原理非常重要。

理解流,使用Node内建的流API,最后创建和测试自己的流。 尽管流模块的概念很抽象,一旦掌握主要概念,就会看到流在各种场合的用途。

流的介绍

在Node中,流是由几个不同的对象附着的抽象接口。谈及流,我们指的是在特定场景中,它们是一个协议。 流是能够读写的, 并且是基于事件实现的一个实例。 流创建对象间数据流的方式,而且能够模块化。

流的类型

流通常包含某种I/O,而且它们能够通过其处理的类型分到相应的组。 如下类型的流出自 James Halliday 的 stream-handbook

  • 内置 —— 许多Node的核心模块都实现了流的接口,例如:fs.createReadStream
  • HTTP —— 除了网络技术的流,还有被设计处理其他网络技术的流
  • 解析器 —— 以前很多解析器都使用流来实现。 比较流行的第三方模块包括XML和JSON解析器
  • 浏览器 —— 事件驱动的Node流可以被扩展使用在浏览器,使用客户端代码提供功能
  • Audio —— James Halliday写了一些有流接口的声音模块
  • RPC(远程调用) —— 通过网络发送流是进程间通信的有效方式
  • 测试 —— 有许多能够友好使用流的测试库和工具用来测试流本身

理解为什么流很重要的重要方式就是想一下没有它们如何处理数据? 接下来我们更详细地对比一下Node的同步、异步和流接口。

为什么时候使用流

当使用fs.readFileSync同步读取一个文件的时候,程序将会被阻塞,所遇的数据将会被读到内存中。 使用fs.readFile将会阻止程序阻塞,因为它是异步方法,但是它仍然会将余留文件数据读到内存中。

倘若有一个方式可以告知fs.readFile去读取一个数据块到内存中,处理它,再去索取更多数据呢? 这就要用到流了。

当处理大文件压缩、归档、媒体文件和巨大的日志文件时, 内存使用就成了问题。取代把剩余文件数据读到内存中,你可以使用fs.read配合一个合适的缓冲区,一次读取固定的长度。或许,你可以使用fs.createReadStream提供的流API。

使用流式的API意味着IO操作可能会使用更少内存

流被设计为异步的方式,相比将剩余文件数据一次性读进内存,还是值得读取一个缓冲的,期望的操作将会被执行,而且结果会被写到输出流。这种方式最接近Node管用的方式。除此之外流使用老版本的JS实现。 使用fs.createReadStream,提供了更多可扩展的方案,但是最终仅仅是用更好的对一些简单的文件系统封装了更友好的API。

流继承事件

每一个流模块的基类都会触发若干事件,主要依赖基类是否可读,可写或者都是可以。 事实是流继承自事件意味着你能够绑定标准事件管理流,或者创建自定义事件来实现特定域的行为。

当使用strea.Readable实例,可读事件是重要的,因为它代表着流已经准备好调用stream.read()。 为数据俘上一个监听器会导致流行为像老的流API,当数据可用时,数据通过数据监听器而不是通过调用stream.read()。 当流在接收数据遇到错误时会触发。

结束事件表示流已经接收到了相当量的结束符,且不会再接受更多数据。还有一个关闭事件表示底层资源已经关闭,与结束事件不同,Node API文档说明不是所有的流都会触发这个事件,因此首选应该是结束事件。

stream.Writable类改变了表示流结束的语意。两者不同点在于writable.end()调用时,结束事件会触发,而关闭意味着IO资源已经关闭,不会一直需要,依赖底层的流。

管道和非管道事件会在当通过一个流到stream.Readable.prototype.pipe方法时触发。这能够用于非管道情况下的适配流行为。监听器接收目标流作为第一个参数,因此这个值会在流变化的时候被检查。

共 20 个模块,1301 篇 Markdown 文档。