Node.js是异步的,并且本质上是事件驱动的。 因此,它非常适合处理I / O绑定的任务。 如果您正在执行I / O操作的应用程序,则可以利用Node.js中可用的流。 因此,让我们详细研究Streams并了解它们如何简化I / O。
什么是流
流是unix管道,可让您轻松地从源读取数据并将其通过管道传输到目标。 简而言之,流不过是EventEmitter
并实现了一些特殊方法。 根据实现的方法,流将变为可读,可写或双工(可读和可写)。 可读流可让您从源读取数据,而可写流可让您将数据写入目标。
如果您已经使用过Node.js,则可能遇到过流。 例如,在基于Node.js的HTTP服务器中, request
是可读流, response
是可写流。 您可能使用过fs
模块,该模块可让您使用可读和可写文件流。
现在您已经了解了基础知识,让我们了解不同类型的流。 在本文中,我们将讨论可读和可写的流。 双工流不在本文讨论范围之内。
可读流
可读流使您可以从源中读取数据。 来源可以是任何东西。 它可以是文件系统上的简单文件,也可以是内存中的缓冲区,甚至可以是另一个流。 由于流是EventEmitters
,因此它们在各个点发出多个事件。 我们将使用这些事件来处理流。
从流阅读
从流中读取数据的最佳方法是侦听data
事件并附加回调。 当有大量数据可用时,可读流将发出一个data
事件,并执行您的回调。 看一下以下代码片段:
var fs = require('fs');
var readableStream = fs.createReadStream('file.txt');
var data = '';
readableStream.on('data', function(chunk) {
data+=chunk;
});
readableStream.on('end', function() {
console.log(data);
});
函数调用fs.createReadStream()
为您提供可读的流。 最初,流处于静态状态。 一旦您侦听data
事件并附加了回调,它就会开始流动。 之后,将读取大块数据并将其传递给您的回调。 流实现者决定data
事件的频率。 例如,一旦读取了几KB数据,HTTP请求就可能发出data
事件。 当您从文件中读取数据时,一旦读取一行,您可能会决定发出data
事件。
当没有更多数据要读取(到达结束)时,流将发出end
事件。 在以上代码段中,我们监听此事件以在结束时得到通知。
还有另一种从流中读取的方法。 您只需要在流实例上重复调用read()
,直到read()
完所有数据块。
var fs = require('fs');
var readableStream = fs.createReadStream('file.txt');
var data = '';
var chunk;
readableStream.on('readable', function() {
while ((chunk=readableStream.read()) != null) {
data += chunk;
}
});
readableStream.on('end', function() {
console.log(data)
});
read()
函数从内部缓冲区读取一些数据并返回。 当没有内容可读取时,它返回null
。 因此,在while循环中,我们检查是否为null
并终止循环。 请注意,当可以从流中读取大量数据时,将发出readable
事件。
设定编码
默认情况下,您从流中读取的数据是Buffer
对象。 如果您正在阅读字符串,则可能不适合您。 因此,您可以通过调用Readable.setEncoding()
在流上设置编码,如下所示。
var fs = require('fs');
var readableStream = fs.createReadStream('file.txt');
var data = '';
readableStream.setEncoding('utf8');
readableStream.on('data', function(chunk) {
data+=chunk;
});
readableStream.on('end', function() {
console.log(data);
});
在以上代码段中,我们将编码设置为utf8
。 结果,数据被解释为utf8
并以字符串形式传递给您的回调。
管道
管道是一种很好的机制,您可以从源中读取数据并将其写入目标,而无需自己管理流程。 看一下以下代码片段:
var fs = require('fs');
var readableStream = fs.createReadStream('file1.txt');
var writableStream = fs.createWriteStream('file2.txt');
readableStream.pipe(writableStream);
上面的代码片段利用了pipe()
函数将file1
的内容写入file2
。 由于pipe()
为您管理数据流,因此您不必担心数据流慢或快。 这使pipe()
成为读取和写入数据的简洁工具。 您还应该注意, pipe()
返回目标流。 因此,您可以轻松地利用此功能将多个流链接在一起。 让我们看看如何!
链式
假设您有一个存档,并想对其进行解压缩。 有多种方法可以实现此目的。 但是最简单,最干净的方法是使用管道和链接。 请看以下片段:
var fs = require('fs');
var zlib = require('zlib');
fs.createReadStream('input.txt.gz')
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream('output.txt'));
首先,我们从input.txt.gz
文件创建一个简单的可读流。 接下来,我们将此流通过管道zlib.createGunzip()
到另一个流zlib.createGunzip()
以zlib.createGunzip()
内容。 最后,由于可以链接流,因此我们添加了可写流,以便将未压缩的内容写入文件。
附加方法
我们讨论了可读流中的一些重要概念。 这是您需要了解的更多流方法:
-
Readable.pause()
–此方法暂停流。 如果流已经在流动,它将不再发出data
事件。 数据将保存在缓冲区中。 如果在静态(非流动)流上调用此流,则该流开始流动,但不会发出data
事件。 -
Readable.resume()
–恢复暂停的流。 -
readable.unpipe()
–这将从管道目标中删除目标流。 如果传递了参数,它将停止将可读流传递到特定目标流中。 否则,将删除所有目标流。
可写流
可写流使您可以将数据写入目标。 像可读流一样,它们也是EventEmitters
并在各个点发出各种事件。 让我们看看可写流中可用的各种方法和事件。
写入流
要将数据写入可写流,您需要在流实例上调用write()
。 以下代码段演示了此技术。
var fs = require('fs');
var readableStream = fs.createReadStream('file1.txt');
var writableStream = fs.createWriteStream('file2.txt');
readableStream.setEncoding('utf8');
readableStream.on('data', function(chunk) {
writableStream.write(chunk);
});
上面的代码很简单。 它仅从输入流中读取数据块,然后使用write()
写入目标。 该函数返回一个布尔值,指示操作是否成功。 如果为true
,则写入成功,您可以继续写入更多数据。 如果返回false
,则表示出了点问题,您目前无法编写任何内容。 可写流将通过发出drain
事件来通知您何时可以开始写入更多数据。
数据结束
当您没有更多数据要写入时,您只需调用end()
即可通知流您已完成写入。 假设res
是HTTP响应对象,则通常需要执行以下操作将响应发送到浏览器:
res.write('Some Data!!');
res.end('Ended.');
当调用end()
且已刷新所有数据块时,流将发出finish
事件。 请注意,调用end()
后无法写入流。 例如,以下内容将导致错误。
res.write('Some Data!!');
res.end();
res.write('Trying to write again'); //Error!
以下是与可写流相关的一些重要events
:
-
error
–表示在写/配管时发生了错误。 -
pipe
–将可读流传输到可写流中时,可写流会发出此事件。 -
unpipe
–在可读流上调用unpipe并停止将其输送到目标流中时发出。
结论
这就是所有关于流的基础知识。 流,管道和链接是Node.js的核心和最强大的功能。 如果负责任地使用,流确实可以帮助您编写简洁而高效的代码来执行I / O。
你喜欢这篇文章吗? 通过评论让我们知道您的想法。