简单认识和使用node 中的流(stream)

286 阅读4分钟

为什么要用流

- 如果整体读写, 文件比较大 ,一次性读取就会占用大量内存,效率低下,而且内存是非常珍贵的,所有就有了流的诞生。
- 流是将数据分隔段 ,一段一段的读取,效率很高

流是什么

- 流是一组有序的 油漆店和终点的字节数据传输手段
- 他不关心整体的内容 只关注是否从文件中读到了数据 以及杜德奥数据之后的处理
- 流(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