1. 可读流使用方法
1.1 引入fs模块
const fs = require('fs');
1.2 创建可读流
fs.createReadStream(path,[options]);
1.3 可读流参数
- path:文件路径
options
- encoding
设置读取编码,默认值:null
,使用buffer方式读取
可选参数:utf8
|ascii
|base64
flags
对文件进行何种操作,默认值:r
可选参数:符号 含义 r 读文件,文件不存在报错 rs 同步读取文件并忽略缓存 mode
操作权限,默认值:0o666
- fd
文件描述符,默认值:null
- autoClose
自动关闭,默认值:true
,对错误或结束的文件描述符将自动关闭
可选参数:true|false
- start
从指定索引开始读取,默认值:0
可选参数:number
- end
读取到指定索引结束(包含索引位)
可选参数:number
- highWaterMark
最高水位线,读取缓存区默认大小:64KB
可选参数:number
- encoding
- 示例
fs.createReadStream('./1.txt',{
flags: 'r', //对文件做何种操作
mode: 0o666, //操作权限
encoding: 'utf-8', //以指定编码读取文件,如果不设置默认以buffer方式读取
start: 3, //从指定索引开始读取
end: 9, //读取到指定索引结束
highWaterMark:3, //水位线,缓存区设置每次读取多少
autoClose: true //自动关闭
});
1.4 事件监听
- open
监听文件打开事件
rs.on('open',()=>{
console.log('打开文件');
});
- data
监听data事件,开始监听data事件,流开始读取文件并发送到回调函数中
rs.on('data',(data)=>{
console.log(data);
});
- error
监听error事件,文件读取失败触发error事件
rs.on('error',(err)=>{
cosole.log('读取失败:'+err);
});
- end
监听end事件,文件读取完成触发end事件
rs.on('end',()=>{
console.log('读取完成');
});
- close
如果设置autoClose为true,当文件执行失败或执行完毕会触发close事件,关闭文件
rs.on('close',()=>{
console.log('关闭文件');
});
1.5 暂停和恢复data
rs.on('data',(data)=>{
console.log(data);
rs.pause(); //暂停读取和发送data事件
setTimeout(() => {
rs.resume(); //恢复读取和发送data事件
}, 2000);
});
2. 可读流实现原理
2.1 创建自定义模块
const EventEmitter = require('events'); //引入事件模块
const fs = require('fs'); //引入文件系统模块
class ReadStream extends EventEmitter{ //创建ReadStream类,继承events事件
constructor(path,options={}){
super();
}
}
module.exports = ReadStream; //导出模块
2.2 自定义模块引入
- 模块引入顺序
模块分为:系统模块、自定义模块、第三方模块
- 系统模块优先级最高
- 自定义模块引入如果没有加’./’就从系统模块里找,系统模块里面找不到就再node_modules文件夹里找
- 第三方模块不需要’./’的形式引入,可以直接通过包名将文件引入,查找package.json中的main对应的文件运行,如果当前目录下没找到,会一直向上一级目录查找,直到找到根目录为止
- 使用自定义ReadStream模块
const ReadStream = require('./ReadStream');
let rs = new ReadStream(path,[options]);
2.3 创建实例
- 在ReadStream类中创建实例
class ReadStream extends EventEmitter{ //创建ReadStream类,继承events事件
constructor(path,options={}){
super();
//创建实例
this.path = path; //文件路径
this.autoClose = options.autoClose || true; //是否自动关闭,默认为true
this.flags = options.flags || 'r'; //操作方式,默认为r
this.encoding = options.encoding || null; //设置编码,默认buffer
this.start = options.statr || 0; //开始读取位置,默认为0
this.end = options.end || null; //读取结束位置,默认为null,读取到最后一位
this.pos = this.start; //计算偏移量
this.highWaterMark = options.highWaterMark || 64*1024; //最高水位线,默认64KB
this.flowing = null; //控制当前是否流动模式
this.buffer = Buffer.alloc(this.highWaterMark); //构建读取到的内容的buffer
//创建可读流,调用打开文件方法
this.open(); //异步执行
//检查用户是否监听了data事件
this.on('newListener',(type)=>{
if(type === 'data'){ //用户监听了data事件,准备开始读取文件
this.flowing = true; //变为流动模式
this.read(); //执行读取文件方法
}
})
}
//'后面的方法都写在这里'
}
2.4 打开文件操作
open(){
fs.open(this.path, this.flags, (err,fd)=>{ //(文件路径,操作方式,CallBackFn(错误,文件描述符))
if(err){
this.emit('error',err); //如果文件出错调用错误方法
if(this.autoClose){ //如果设置自动关闭,文件出错将文件关闭
this.destroy(); //执行销毁方法(触发close事件)
}
return; //终止文件打开操作
}
this.fd = fd; //如果没错误就讲获取的fd保存起来
this.emit('open'); //触发文件打开事件
});
}
2.5 销毁文件操作
destroy(){
if(typeof this.fd === 'number'){ //判断文件是否打开过,如果打开后出错执行fs.close方法将文件关闭
fs.close(this.fd,()=>{ //异步操作,文件还在监听,需要执行close事件
this.emit('close');
});
return;
}
this.emit('colse'); //如果没有fd证明文件没有被打开过,可以直接执行close事件
}
2.6 读取文件操作
read(){ //同步
if(typeof this.fd !== 'number'){ //检查是否获取到fd,如果没获取到就证明文件还没被打开
return this.once('open',()=>this.read()); //如果文件没打开就继续调用自己,等文件打开后再继续执行下一步
}
//计算读取范围,如果没有this.end就读取到最后一位,如果有就计算偏移量
let howMuchToRead = this.end ? Math.min(this.highWaterMark, this.end - this.pos + 1) : this.highWaterMark;
//获取到fd后开始读取 read(文件描述符,读取到哪个buffer,读取到buffer的哪个位置,往buffer读取几个,读取的位置)
fs.read(this.fd, this.buffer, 0, howMuchToRead, this.pos, (err,bytesRead)=>{
if(bytesRead > 0){ //读取到内容
this.pos += bytesRead; //计算偏移量
let r = this.buffer.slice(0,bytesRead); //保留有用的
r = this.encoding ? r.toString(this.encoding) : r; //判断是否设置编码,按照指定编码转换成字符串
this.emit('data',r); //第一次读取
if(this.flowing) this.read(); //如果是流动的就一直读取
}else{
this.emit('end'); //读取完成
this.destroy(); //销毁
}
});
}
2.7 暂停读取操作
pause(){
this.flowing = false; //关闭流动模式
}
2.8 恢复读取操作
resume(){
this.flowing = true; //开启流动模式
this.read(); //继续读取
}