# stream(流)

241 阅读2分钟

stream(流)

流(stream)是 Node.js 中处理流式数据的抽象接口。 stream 模块用于构建实现了流接口的对象。

上面对流的解释是Node.js官方的话语,我们的前端的同学估计不少人其实对抽象接口是不太有具体的印象的,所以看完上面的解释对于stream是干嘛的其实还是蒙圈的,下面我就先给大家解释下何为抽象接口,然后再来学习stream。

先说个故事

有一个社会闲散人士叫张三,整天游手好闲,不务正业,身无长技。突然有一天感觉到自己年过30但是感觉还是没有一份事业,很空虚,很寂寞。所以他决定要做一份自己的事业,这个事业最好能帮助其他人让社会变得更美好,最好这个事业还能够给他带来不错的收入。所以他决定要做一名程序员,而且是一个技术过硬的程序员,但是他不会编程,所以他要先去学习编程,说道学习编程就想到了业内最棒的培训机构:千锋教育。于是背上行囊来到了距离他家最近的千锋教育的南京校区学习,经过5个月的学习终于神功大成,找到了一份web前端程的工作,从此开启新的人生。

通过故事学习何为接口

故事中的张三是一个很普通的人我们把他看成是一个对象,这个对象现在只有一些作为人的基本功能,使用代码表示为:

    class Person{
        constructor(name,age){
            this.name = name
            this.age = age
        }       
        show(){
            console.log(`我是${this.name}`)
        }
        eat(){
            console.log(`吃饭`)
        }
        sleep(){
            console.log(`睡觉`)
        }
    }

    const p1 = new Person("张三",30);

通过代码我们可以看出张三作为一个对象来说仅仅只是具备了“人类”的基本功能,想要成为程序员还不行,因为他不会编程。所以这时候我们可以定义一个程序员的类和一个编程的接口: Programmer 。伪代码如下:

    // 编程接口
    interface Programming{
        // 编程功能
        // 参数type为编程的类型,例如: H5,JAVA,C++,C#...
        // 该方法为:抽象方法,具体表现为没有函数体
        function pro(type){}
    }

    // 程序员的类必须实现编程接口
    class Programmer implement Programming{
        constructor(name,age){
            this.name = name
            this.age = age
        }       
        show(){
            console.log(`我是${this.name}`)
        }
        eat(){
            console.log(`吃饭`)
        }
        sleep(){
            console.log(`睡觉`)
        }
        // 想要实现编程接口,必须重写接口中的方法
        pro(type){
            console.log(`学习了${type}技能`)
        }
    }
    const p1 = new Person("张三",30)
    p1.pro("H5");

程序员必须要会编程,这是咱们业内的约定,所以张三想要程序员就需要把编程给学了,对应的上面的伪代码就是Programmer类必须实现Programming接口中的抽象方法,所以我们再来看接口和抽象的意思就很明显了。

  • 接口:一种能力的约定,不会有具体的实例,用于扩展类的功能,类想要实现接口必须实现了接口中的能力。
  • 抽象:不具体,不固定的。例子中的pro()方法就是抽象方法,它只是为了要求实现接口的类必须要有这个方法,但是这个方法以后具体的函数体就由类自己决定了。

*** 再看stream ***

上面我们了解了何为抽象接口了之后再看stream就能明白了,其实stream就是为了扩展Node.js中其他的模块而存在的,自己不会有具体的实现,但是为其他的类和模块提供了功能。

stream基础

在Node.js中有四种基本的流类型:

  • Writable - 可写入数据的流(例如 fs.createWriteStream())。
  • Readable - 可读取数据的流(例如 fs.createReadStream())。
  • Duplex - 可读又可写的流(例如 net.Socket)。
  • Transform - 在读写过程中可以修改或转换数据的 Duplex 流(例如 zlib.createDeflate())。

上面四个类型的流都实现了stream接口,所以他们也都实现了接口中的抽象方法,各自如下:

  • Writable 需要重写 _write()方法。
  • Readable 需要重写 _reed()方法。
  • Duplex 需要重写 _write()和_reed()方法。
  • Transform 需要重写 _transform()和 _flush()方法。

pipe

无论是哪一种流,都会使用.pipe()方法来实现输入和输出。
.pipe()方法就是一种简历管道的方法来输出和输入流。
.pipe()函数很简单,它仅仅是接受一个源头src并将数据输出到一个可写的流dst中:

    // src表示可读流
    // dest表示可写流
    src.pipe(dst);

Readable流

Readable流可以产出和读取数据,你可以将这些数据传送到一个writable,transform或者duplex流中,只需要调用pipe()方法即可,下面我们来实现一个具体的案例:

   const Readable = require('stream').Readable;
   // 创建可读流对象
    const rs = new Readable();
    // 往流中推入字符串 "a"和换行符
    rs.push('a \n');
    // 往流中推入字符串 "b"
    rs.push('b');
    // 告诉流可以结束工作了
    rs.push(null);
    // 将流中的数据输出到可写流中,也就是输出cmd
    rs.pipe(process.stdout);

代码执行结果如下:

    a
    b

当然像上面这么写有点麻烦,我们也可以按照自己需求连续的往流中推入数据,代码修改如下:

    const Readable = require('stream').Readable;
    const rs = Readable();
    // 重写_read方法
    rs._read = function(){
        for( let i = 65;i<91;i++ ){
            rs.push( String.fromCharCode(i) )
        }
        rs.push(null)
    };

    rs.pipe(process.stdout);

代码执行结果如下:

    ABCDEFGHIJKLMNOPQRSTUVWXYZ

Writeable流

writable可写流指的是只能流进不能流出的流。
我们可以通过writeable可写流将数据输出到控制台,下面通过一个小案例来认识它:

    const Writable = require('stream').Writable
    // 创建一个可写流对象
    const ws = Writable()
    let count = 1
    ws._write = function (chunk, enc, next) {
        // 用户在cmd中没有输入其他内容直接按下回车时,结束当前进程
        if( chunk.toString() === "\r\n" ){
            process.exit()
        }
        console.log(`第${count++}次写入数据:${chunk}`);
        // next()表示还可以继续再次接受数据
        next();
    };

    process.stdin.pipe(ws);

当然一般需要通知可写流数据已经写完了肯定不是直接关闭进程来解决,正常的做法应该是调用**.end()**来实现的,所以上面的关闭进程的代码应该修改成:

     if( chunk.toString() === "\r\n" ){
            // process.exit()
            ws.end()
        }