数据流(Stream):是处理系统缓存的一种方式。操作系统采用数据块(chunk)的方式读取数据,每收到一次数据,就存入缓存。
Node应用程序有两种缓存的处理方式:
(1)传统方式:等到所有数据接收完毕,一次性从缓存读取。这种方式先将数据全部读入内存,然后处理,如果遇到大文件,要花很长时间,才能进入数据处理的步骤。
(2)“数据流”方式:每收到一块数据,就读取一块,即在数据还没有接收完成时就开始处理它。
这种方式每次只读入数据的一小块,每当系统读入一小块数据,就会触发一个事件,发出“新数据块”的信号。应用程序只要监听这个事件,就能掌握数据读取的进展,做出相应的处理,这样可以提高程序的性能。
在node中,有四种类型的数据流:
(1)Readable
:用于读操作( fs.createReadStream())
(2)Writeable
:用于写操作(fs.createWriteStream())
(3)Duplex
:用于读取和写入操作
(4)Transfrom
:在读写过程中可以修改和变换的Duplex流
所有的Stream对象都是EventEmitter的实例。常用的事件有:
data
:当有数据可读时触发end
:没有更多的数据刻度时触发error
:在接收和写入过程中发生错误时触发finish
:所有数据已被写入到底层系统时触发Node.js中的流最大的作用是:读取大文件的过程中,不会一次性的读入到内存中。每次只会读取数据源的一个数据块(收到一个数据,就读取一块,即在数据还没有接收完就开始处理)。
然后后续过程中可以立即处理该数据块(数据处理完成后会进入垃圾回收机制),而不用等待所有的数据。
使用流的好处:
文件系统中读取文件的方式readfile()
,会把文件内容整个的读进以 Buffer格式存入到内存中,然后再写进返回对象,那么这样的效率非常低的,并且该文件如果是好几个G的大文件,那么内存会直接被卡死掉的,或者服务器直接会奔溃掉。
而使用流的createReadStream()
方法就可以避免占用内存多的情况发生,在读取大文件的过程中,不会一次性的读入到内存中。每次只会读取数据源的一个数据块,这就是流的优点。
文件输入流即通过流读取文件,所需步骤有:
(1)导入文件模块
(有关文件模块,后续会有相关博客说明)
(2)创建一个输入流
(从键盘输入或从文件读取)
(3)设置输入流的字符集
(编码格式)
(4)绑定流事件
例如:
var fs=require('fs')
var data=''
//创建输入流
var readerStream=fs.createReadStream('input.txt')
readerStream.setEncoding('utf8')
readerStream.on('data',chunk={
data+=chunk
})
readerStream.on('end',()=>{
console.log(data)
})
readerStream.on('error',err=>{
console.log(err.stack)
})
console.log('程序结束')
//程序结束
//文件内容
从这个过程我们也可以看出:这个过程是非阻塞的I/O
文件输出流即通过流向文件中写内容,所需步骤有:
(1)导入文件模块
(2)创建一个输出流(写入流)
(3)设置写入数据的字符集
(4)文件结束退出
(5)绑定流事件
例如:
var fs=require('fs')
//创建输出流
var writeStream=fs.createWriteStream('input.txt')
writeStream.write('Hello World','utf8')
writeStream.end()
writeStream.on('finish',()=>{
console.log('写入完成')
})
writeStream.on('error',err=>{
console.log(err.stack)
})
console.log('程序结束')
//程序结束
//写入完成
这也是一个非阻塞的I/O
管道提供了一个输出流到输入流的机制,通常我们用于从一个流中获取数据并将数据传递到另外一个流中。
所需步骤有:
(1)导入文件模块
(2)创建一个输入流
,用于读取一个文件的内容
(3)创建一个输出流
,用于向另一个文件写入内容
(4)创建管道流
例如:将input文件里面的内容传输到output文件中
var fs=require('fs')
var readerStream=fs.createReadStream('input.txt')
var writeStream=fs.createWriteStream('output.txt')
readerStream.pipe(writerStream)
console.log('程序结束')
链式是一个机制,一个流的输出连接到另一个流,并创建一个链多流操作(相当于接了好几根管道)。它通常用于管道的操作。
可以用于压缩文件以及文件解压,所需步骤:
(1)导入文件模块
(2)导入压缩解压缩模块
(3)压缩或解压缩文件
例如:压缩文件
var fs=require('fs')
var zlib=require('zlib')
fs.createReadStream('input.txt').pipe(zlib.createFzip()).pipe(fs.createWriteStream('input.zip'))
console.log('程序结束')
文件解压缩
fs.createReadStream('input.zip').pipe(zlib.createGunzip()).pipe(fs.createWriteStream('input.txt'))
console.log('程序结束')
一个例子,我们在线看视频的时候。如果你的网络足够快,数据流(stream)就可以足够快,可以让buffer迅速填满然后发送和处理,然后处理另一个,再发送,再另一个,再发送,然后整个stream完成。
但是当你网络连接很慢,当处理完当前的数据后,你的播放器就会暂停,或出现”缓冲”(buffer)字样,意思是正在收集更多的数据,或者等待更多的数据到来,才能下一步处理。当buffer装满并处理好,播放器就会显示数据,也就是播放视频了。在播放当前内容的时候,更多的数据也会源源不断的传输、到达和在buffer等待。
如果播放器已经处理完或播放完前一个数据,buffer仍然没有填满,”buffering”(缓冲)字符就会再次出现,等待和收集更多的数据。