理解node stream

学好node,学好stream。stream就是流。基本上大大小小的node程序都用到了stream。

stream的官方教程在这里,本文就不重复介绍Stream中的API了。只是讲讲我对Stream的一些理解,希望能让读者更好的理解Stream。

先看个node读文件的例子:

1
2
3
fs.readFile('./ss.js', (err, data) => {
console.log(data.toString());
});

正常文件读取就是调用fs.readFile然后异步获取文件数据data。这种方式就是把文件的内容都读取出来,然后放在内存中操作。于是就会产生一个问题。当文件非常大的时候,读取出来放在内存中明显是不合适的。
那么怎么解决这个问题呢?
一个办法就是读取一部分数据,然后处理完之后,再读取一部分数据。这样不论文件有多大都能处理掉。
Stream就是这样的实现方式。改一下上面读文件的写法:

1
2
3
4
5
6
7
8
let data;
fs.createReadStream('./ss.js')
.on('data', chunk => {
data += chunk;
})
.on('end', () => {
console.log(data.toString());
});

用fs.createReadStream创建一个Stream对象。然后监听Stream对象的data和end事件。data事件会在读数据的时候触发,end事件会在数据读完之后触发。
然而上面的例子也是同样把所有的数据都读到了内容中然后输出。再改个写法:

1
2
3
4
5
6
fs.createReadStream('./ss.js')
.on('data', chunk => {
process.stdout.write(chunk);
})
.on('end', () => {
});

这样写就正真做到了按块处理数据。因为process.stdout是个WriteStream。可以把数据一块一块交给它处理。
当这样写的时候还有问题,就是当WriteStream处理速度比ReadStream慢的时候。就会出现上一个块数据还没处理好,下一块数据就来了。造成数据溢出。可以通过判断WriteStream.write的返回值。来暂停/开启读取,以此来控制速度。当然还有简单的方式那就是pipe。再改下:

1
fs.createReadStream('./ss.js').pipe(process.stdout);

pipe内部实现了。读取数据速度的控制,保证丢失数据。

最后给一个非常形象的pipe管道的图。

这是个双通道的管道,即可读又可写。