为什么要用流
- 如果整体读写, 文件比较大 ,一次性读取就会占用大量内存,效率低下,而且内存是非常珍贵的,所有就有了流的诞生。
- 流是将数据分隔段 ,一段一段的读取,效率很高
流是什么
- 流是一组有序的 油漆店和终点的字节数据传输手段
- 他不关心整体的内容 只关注是否从文件中读到了数据 以及杜德奥数据之后的处理
- 流(stream)在node.js中是一个抽象接口 被node中的很多对象所实现
- 流是可读 可写的 所有的流都是EventEmitter 的实例
流的分类
- 可读流(Readable Stream)
- 可写流(Writeable Stream)
- 双工流(Duplex Stream)
- 转换流(Transform Stream)
知道了一些流的基本情况 那么我们就来了解一下怎么使用流
可读流
- 首先先看一下可读流的api
let rs=fs.createReadStream(path,options) //创建一个可读流
/*
* path 读取文件的路劲
* options 参数
* - flags 打开文件要做的操作 默认为 ‘r’
* - encoding 读取文件的编码 默认为 null
* - start 开始读取的索引位置
* - end 结束读取的索引位置 (含结束的位置)
* - highWaterMark 读取缓存默认的大小 默认值为64K 1024*64
*/
Readbale 的事件
- data事件 数据正在传递是 触发该事件
- end 事件 数据传递完成后 触发事件
- open 时间 打开文件
- close 事件 关闭文件 触发事件
- error 报错后触发事件
- rs.pause rs.resume 暂停和恢复触发
配合可读流的api和事件 我们简单些一个小例子
//1.txt 内容 123你好世界
let fs =require('fs');
let rs=fs.createReadStream('./1.txt',{
flags:'r',
start:3,
end:10,
highWaterMark:3
})
rs.on('open',(data)=>{
console.log('打开文件')
})
//监听data事件
rs.on('data',(data)=>{
console.log(data)
})
//结果 //<Buffer e4 bd a0> //.toString() 你
//<Buffer e5 a5 bd> //.toString() 好
//<Buffer e4 b8>
//结果分析 首先 flags 是 r 所以是可读 start:3 就是从第三个开始 highWaterMark:3 每次读三个 然后 end : 10 一共读 10个 所以 第一次 读 的正好是 ‘你’ 第二次 是 ’好‘
第三次是中文转的buffer的两个字节 共(10-3+1)8个字节
//监听end事件
rs.on('end',(data)=>{
console.log('结束')
})
// 结果//结束
rs.on('close',(data)=>{
console.log('关闭')
})
rs.setEncoding('utf8'); //设置编码 等同{encoding:'utf8'}
在 data事件的时候可以触发暂停事件
re.on('data',(data)=>{
res.pause() // 如果是上边用了暂停的话只会触发一次 consosle.log(data)
})
rs.resyme() //恢复触发data事件
可写流
-还是看一下他的api
let ws=fs.createWriteStream(path,options)
/*
path 写文件的路劲
options
flages 打开文件要做的操作 默认为w
encoding 默认为utf8
hifgWaterMark 写入缓存区的大小 默认16kb
*/
Writeable 的方法
- write
- end
- drain
- finish
ws.write(chunk,encoding,callback)
ws.end(chunk,encoding,callback)
/*
chunk 写入的内容 buffer 或者string
encoding 写入的编码 默认utf8
callback回调
*/
drain 方法 (中文的意思是排水,排干)
- drain 方法是监测写入的内容是不是在内存中还存在 只有内存中不存在写入的内容才触发 drain 方法
举一个例子
let fs=require('fs');
let ws=fs.createWriteStream('2.txt',{
falgs:'w',
highWaterMark:3
})
let i=10
function write(){
let flag=true;
while(i&&flag){
flag=ws.write(i+"");
i--;
console.log(flag)
}
}
write();
ws.on('drain',()=>{
console.log('排干');
write();
})
//一共执行 排干 3次
finish 方法
在调用了 stream.end() 方法,且缓冲区数据都已经传给底层系统之后, 'finish' 事件将被触发。
还是上边那段代码 然后后边加上
ws.end('结束');
ws.on('finish',()=>{
console.log(111);
})
// 排干 没有被执行 2.txt 里边是 109结束
//执行结果是 true false 111
上边就是读和写流的方法 那么我们接着往下介绍
pipe 的原理 和用法
- gulp很多都用了这个方法
//原理
/*
从一个文件读取,到另一个文件
从一个文件读取部分(1.txt),读取的部分写入到另一个文件(2.txt);
读取的一部分在内存上如果没有写完 就等待读取,等什么时候写完再继读 写 直到1的文件内容读到2文件内 关闭 文件
*/
let fs= require('fs');
let rs=fs.createReadStream('./1.txt');
let ws=fs.createWriteStream('./2.txt');
rs.on('data',(data)=>{
var flag=ws.write(data);
if(!flag){
rs.pause();
}
});
ws.on('drain',()=>{
rs.resume();
})
rs.on('end',()=>{
ws.end();
})
// 使用
rs.pipe(ws);
Duplex
Duplex 双工流 简单实现
```
let {Duplex} = require('stream');
class MyDuplex extends Duplex{
_read(){
}
_write(chunk,encoding,callback){
callback();
}
}
```
Transform 转换流
简单实现
let {Transform} = require('stream');
class MyTransform extends Transform{
_transform(chunk,encoding,callback){
console.log(chunk.toString().toUpperCase());
callback();
this.push('123');
}
}
let myTransform = new MyTransform();
process.stdin.pipe(myTransform).pipe(process.stdout);
以上就是stream的简单介绍,我就写到这里,下一节,我们来自己实现一个 readStream 和writeStream