流是什么
- 流是一组有序的,有起点和终点的字节数据传输手段
- 它不关心文件的整体内容,只关注是否从文件中读到了数据,以及读到数据之后的处理
- 流是一个抽象接口,被 Node 中的很多对象所实现。比如HTTP 服务器request和response对象都是流。
流的分类
- Readable - 可读操作。
- Writable - 可写操作。
- Duplex - 可读可写操作.
- Transform - 操作被写入数据,然后读出结果。
所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:
- data - 当有数据可读时触发。
- end - 没有更多的数据可读时触发。
- error - 在接收和写入过程中发生错误时触发。
- finish - 所有数据已被写入到底层系统时触发。
可读流
const rs = fs.createReadStream(path,[options]);
/*
highWaterMark 表示一次可以读入多少字节
*/
let rs = fs.createReadStream(path.join(__dirname,'1.txt'),{
flags:'r', // 文件的操作是读取操作
mode: 0o666, //权限位
encoding:'utf8', // 默认是null null代表的是buffer
autoClose:true, // 读取完毕后自动关闭
highWaterMark:3,// 默认是64k 64*1024 byte
start:0, // 123 456 789
//end:3 // 包前又包后
});
复制代码
可读流有两种模式 两种模式可以来回切换
- flowing (流动模式) ----- 这种模式不经过缓存区在完成之前不会停止
rs.on('data',function(data){
console.log(data);
});
复制代码
- paused(暂停模式 默认)----- 这种模式在读取达到
highWaterMark
时候会暂停等待消费 我们在data事件中,可以通过rs.pause()
来暂停读取。暂停了之后,通过rs.resume()
可以恢复读取。
let fs = require('fs');
let path = require('path');
// 返回的是一个可读流对象
let rs = fs.createReadStream(path.join(__dirname,'1.txt'),{
flags:'r', // 文件的操作是读取操作
encoding:'utf8', // 默认是null null代表的是buffer
autoClose:true, // 读取完毕后自动关闭
highWaterMark:3,// 默认是64k 64*1024b
start:0, // 123 456 789
//end:3 // 包前又包后
});
// rs.setEncoding('utf8');
rs.on('open',function(){
console.log('文件打开了')
});
rs.on('close',function(){
console.log('关闭')
});
rs.on('error',function(err){
console.log(err);
});
// newLisenter
rs.on('data',function(data){ // 暂停模式 -> 流动模式
console.log(data);
rs.pause(); // 暂停方法 表示暂停读取,暂停data事件触发
setTimeout(function() {
rs.resume();
}, 1000);
});
rs.on('end',function(){
console.log('end')
});
复制代码
pause方法
在 paused 模式下,必须显式调用 stream.read()
方法来从流中读取数据片段。举个栗子:
let fs = require('fs');
let path = require('path');
let rs = fs.createReadStream(path.join(__dirname,'1.txt'),{
highWaterMark:3
});
// 当我只要创建一个流 就会先把缓存区 填满,等待着你自己消费
// 如果当前缓存区被清空后会再次触发readable事件
// 当你消费小于 最高水位线时 会自动添加highWater这么多数据
rs.on('readable',function(){
let result = rs.read(1);
console.log(rs._readableState.length); // 缓存区的个数
setTimeout(function(){
console.log(rs._readableState.length);
},1000)
});
复制代码
pipe
名字理解管道 可读流给我们提供了一个pipe方法,可以实现 边读边写
当读的快消化的慢的 时候 读取的文件会放入内存中,这样的话内存占用就会越来越大 所以我们需要一个方法在我们消化玩之前暂停读取,消化完之后继续
举个例子我们会有这种操作 读4个写1个,会不断地产生内存。因此需要写入1个后,暂停读入,直到下一个抽干了,内存中没有了,再继续读入。
let fs = require('fs');
let path = require('path');
let rs = fs.createReadStream(path.join(__dirname,'./1.txt'),{
highWaterMark:4
});
let ws = fs.createWriteStream(path.join(__dirname,'./2.txt'),{
highWaterMark:1
});
rs.on('data',function(chunk){ // chunk 读到的内容
let flag = ws.write(chunk);
if(!flag){
rs.pause();
}
});
ws.on('drain',function(){
console.log('抽干了')
rs.resume();
});
复制代码
NODE提供了更简单的方式:pipe,用法就是:
let fs = require('fs');
let path = require('path');
let rs = fs.createReadStream(path.join(__dirname,'./1.txt'),{
highWaterMark:4
});
let ws = fs.createWriteStream(path.join(__dirname,'./2.txt'),{
highWaterMa
rs.pipe(ws);
复制代码
可写流
当然有可读流 就有可写流
如何创建一个可写流
let fs = require('fs');
let ws = fs.createWriteStream('./1.txt',{
flags:'w',
mode:0o666,
autoClose:true,
highWaterMark:3, // 默认写是16k
encoding:'utf8',
start:0
});
// 写入的数据必须是字符串或者buffer
ws.write(1+'','utf8',()=>{}); // 异步的方法
复制代码
ws.write返回值
let fs = require('fs');
let ws = fs.createWriteStream('./1.txt',{
flags:'w',
mode:0o666,
autoClose:true,
highWaterMark:3, // 默认写是16k
encoding:'utf8',
start:0
});
// 写入的数据必须是字符串或者buffer
let flag = ws.write(1+'','utf8',()=>{}); // 异步的方法
console.log(flag);
flag = ws.write(1+'','utf8',()=>{}); // 异步的方法
console.log(flag);
flag = ws.write(1+'','utf8',()=>{}); // 异步的方法
console.log(flag);
复制代码
上面的结果输出为:true true false。
flag并不是是否写入,而是能否继续写,但是返回false 也不会丢失,就是会把内容放到内存中。所以在当第3次的时候,就是3个字符已经写满了,因此返回false。
drain事件
当都写入完后会触发drain事件。必须缓存区满了,满了后被清空了才会出发drain。
ws.on('drain',function(){
console.log('drain')
});
复制代码
下面简单实现一下 可写流的思路
let fs = require ('fs');
let EventEmitter = require ('events');
class WriteStream extends EventEmitter {
constructot(path,options){
super(path,options);
this.flags = options.flags || 'w';
this.path = path;
this.start = options.start || 0;
this.pos = this.options.start;
this.highWaterMark = options.highWaterMark || 16 *1024
this.autoClose = options.autoClose;
this.encoding = options.encoding || 'utf8';
this.mode = options.mode || 0o666;
this.buffers = []; //缓存区
this.writing = false; // 表示内部正在写入数据
this.length = 0; // 表示缓存区字节的长度
this.open()
}
open(){
fs.open(this.path,(err,fd)=>{
if(err){
if(this.autoClose){
this.destroy()
}
this.emit('error', err)
}
this.fd = fd
})
}
// 如果底层已经在写入数据的话,则必须当前要写入
write(chunk,encoding,cb){
let len = chunk.length;
this.length += len;
let ret = this.length <this.highWaterMark;
if(this.writing){
chunk = Buffer.isBuffer(chunk)?chunk:Buffer.from(chunk,this.encoding);
this.buffers.push({
chunk,
encoding,
cb
})
}else{
// 地层写完 当前数据后要清空缓存区
this.writing = true;
this._write(chunk,encoding,()=>this.clearBuffer())
}
return ret
}
_write(chunk, encoding,cb){
if(typeof this.fd != 'number'){
return this.once('open',()=>this._write(chunk, encoding,cb))
}
fs.write(this.fd,chunk, 0, chunk.length,this.pos,(err,bytesWritten)=>{
if(err){
}
// 写入多少字母 缓存区减掉多少
this.pos += bytesWritten
this.length -= bytesWritten
cb && cb()
})
}
clearBuffer(){
let data = this.buffers.shift();
if(data){
this._write(data.chunk,data.encoding,()=>this.clearBuffer())
}else{
this.writing =false
// 文件写没了
this.emit('drain');
}
}
destroy() {
fs.close(this.fd,(err)=>{
this.emit('error',err)
})
}
}
复制代码
随笔记录 后面会慢慢完善这篇文档!