Node.js流的基本介绍

123 阅读4分钟

什么是流

流是支持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()