stream 浅析

流是什么

  • 流是一组有序的,有起点和终点的字节数据传输手段
  • 它不关心文件的整体内容,只关注是否从文件中读到了数据,以及读到数据之后的处理
  • 流是一个抽象接口,被 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)
        })
    }
}
复制代码

随笔记录 后面会慢慢完善这篇文档!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值