我运行一个节点服务器,并有以下代码:
var readable = fs.createReadStream(__dirname + '/greet.txt',
{encoding: 'utf8', highWaterMark: 332 * 1024});
greet.txt:
hello
我很难理解可读流和可写流;在上面的代码中,我有一个可读的流,它从greet.txt读取-块进入缓冲区,我可以看到二进制数据。。。问题是,难道不应该有一个可写的流将数据发送到另一端的缓冲区吗?二进制数据是如何突然飞入我的缓冲区的,只是不清楚。
以下是可读写的组合:
var readable = fs.createReadStream(__dirname + '/greet.txt',
{encoding: 'utf8', highWaterMark: 332 * 1024});
var writeable = fs.createWriteStream(__dirname + '/greetcopy.txt');
readable.on('data', function(chunk){
writeable.write(chunk);
});
当区块到达可读缓冲区,并通过事件发送到可写流的缓冲区时,可写流是否也应该可读以接收数据?一旦可写流的缓冲区从可读流中获取信息并将其发送到greetcopy.txt文件(该文件为空),数据是如何到达的?
节点中可读写的概念被过度简化了,我很难理解它们。谢谢你抽出时间,我想了解一些幕后发生的事情。。。
Node.js流极其复杂和混乱。我花了大量时间试图理解它们,下面我将尝试传达我的发现。
有5种类型,可读、可写、双工、转换和直通。
好的,简单的部分首先:可读和可写
可读
- 要将数据添加到可读流中,可以使用.push()函数。当流结束时,您按下(null)
- 当结束时,可读流触发"结束"事件
- 您可以从可读流中读取数据,方法是侦听"readable"事件,然后执行"read()",直到它返回null
- 可读流有一个缓冲区,这意味着当你向缓冲区"push()"时,如果缓冲区已满,那么push(()将返回false。但是,即使缓冲区已满,您也可以继续推到缓冲区并填充它。"highWaterMark"(或缓冲区大小)实际上是提供信息的
- 可读流实现了一个_read()方法来从非流源中提取数据。不过,您不必使用此选项。您可以将此方法留空,然后使用前面描述的push方法。使用流的用户可以调用read(),它首先从内部缓冲区读取,然后在缓冲区为空时调用_read()
可写
- 要将数据添加到可写流中,可以使用.write()函数。流结束后,使用.end()
- 当您调用.end()时,它不会立即结束流。它将使用process.nextTick()在下一个tick结束流!这让我在比赛中感到心痛
- 可写流有一个缓冲区。如果缓冲区已满(highWaterMark),那么当您调用.write()时,它将返回false。但是,如果您愿意,可以继续向它写入并忽略此事件。否则,我认为会有一个类似"耗尽"的事件通知你可以继续写作
- 可写流实现了一个_write()方法,将数据发送到一些后端非流接收器。如果此方法返回false,则可写流将开始缓冲数据,并且在"drain"之前不会再次调用_write()
同时使用可读流和可写流
- 您只能通过管道将一个可读流传输到一个可写流。这可能会让您感到困惑,因为您可能已经看到诸如"streamA.pipe(streamB).pipe(streamC)"等语法。事实是,本例中唯一的可读流是streamA。唯一可写入的流是streamC。streamB(以及介于两者之间的任何其他流)是一种特殊类型的流,称为Transform流
- 关键点1:无法通过管道传输到可读流。一切都必须从可读流开始
- 关键点2:不能通过管道将可写流传输到其他任何内容。可写流是它的结束位置。数据必须通过_write方法()退出可写流
让流通过管道相互连接的唯一方法是使用转换流。和我一起走了这么远?以下是它变得极其混乱的地方:双工、转换和通过
双工
- 双工流是可读写的组合流。当您通过管道传输双工流(或从双工流读取)时,它将作为可读流运行。当您通过管道连接到双工流时,它的操作方式与可写流的操作方式完全相同
- 要点1:示例"streamA.pipe(duplexB).pipe(streamC)"意味着从Readable streamA的_read()方法读取数据,并将数据发送到duplex B的_write()方法。它不会转到流C。这也意味着从duplexB的_read()方法读取的数据将进入streamC。语法令人困惑,因为数据看起来像是从流a到流C的一行
- 关键点2:在使用双工流时,是否调用.prush(null)或.end()来结束流是非常令人困惑的。你应该听"结束"还是"结束"事件,这也非常令人困惑。我仍然没有答案。调用end()是否隐式执行.push(null)
这两个关键点都使使用双工流变得非常混乱。事实上,我想要一个与上面完全一样工作的双向流,所以我在这里创建了自己的流。我称之为"链接流",它实际上并不使用_read或_write方法。在全双工模式下,它从streamA获取数据并将其传输到streamC,反之亦然,您可以监听"finish"或"end"事件,这无关紧要。这是一个真正的双向直通管道。
转换
- 转换流是双工流
- 在转换流上调用write()会在封面下调用_write,它只会调用_read()
- 在转换流上调用this.push(…)会在封面下调用_read,后者会调用_transform()
- 基本上所有的数据路径都指向_transform()方法。实现transform方法(_T)。无论您如何使用流,它都可以作为可读或可写的流,并且数据总是放在同一个位置,_transform()方法
- 一旦调用了_transform方法,数据就会被发送到通过管道传输到的任何可写流
通过
- 这只是一个Transform流,在_Transform方法中不执行任何操作
好了。我真的希望Joyent的人能清理Duplex,让它不那么混乱,我也真的希望他们能添加双向PassThrough,这样我就不必使用我上面描述的链接流方法了。
祝你好运!
它们的概念相当简单,您在这里似乎有点困惑。流通常是Unix管道,允许您从源读取数据并通过管道将其发送到目标。实际上,每种类型的流都是一个EventEmitter,它实现了一些特定的方法,并基于这些方法将它们划分为不同类型的流,如可写、可读、转换等。
因此,关于可写流,从这里的官方文档
可写流接口是目的地的抽象,您正在将数据写入。
关于可读流,请参阅此处的官方文档:
Readable流接口是从读取的数据源的抽象。换句话说,数据来自可读流。
因此,使用可写流,您将数据写入目标,而使用Readable,您将从源读取数据。我不相信它会比这更简单,因为我将开始围绕同样的句子进行循环。
基于以上问题的答案
为了接收数据,可写流不应该也是可读的吗?
就是否定的,因为它不会从事件中"接收"数据,正如您所看到的,您正在使用方法.write(chunk);
来源
http://www.sitepoint.com/basics-node-js-streams/
http://maxogden.com/node-streams.html