Node 基础 7:Stream 流

127 阅读3分钟

释义

stream 流

stream 可以每次传一小段数据,数据是二进制的 Buffer,不是字符串 image.png

pipe 管道

image.png

通过事件实现管道

pipe 实际上是监听第一个流的 data 事件,然后写到第二个流。

// stream1 一有数据就塞给 stream2
stream1.on('data',(chunk)=>{'
  stream2.write(chunk)
})

// stream1 停了,就停掉 stream2
stream1.on('end',()=>{
  stream2.end()
})

Stream 对象的原型链

s = fs.createReadStream(path)

那么它的对象层级为:

1、自身属性(由 fs.ReadStream 构造);

2、原型:stream.Readable.prototype;

3、二级原型:stream.Stream.prototype;

4、三级原型:events.EventEmitter.prototype;

5、四级原型:Object.prototype.

Stream 对象都继承了 EventEmitter ,所以可以 on / emit 等。

支持的事件和方法

image.png

stream 分类

1、Readable :可读;

2、Writable :可写;

3、Duplex :可读可写(双向);

4、Transform :可读可写(变化)

  • duplex 和 transform 的区别: duplex 读到的数据和写的数据,一般不是同一个数据;transform 读写的是同一个数据,但是将数据做了一个转换 image.png

Readable Stream

  • 静止态 paused 和流动态 flowing 1、默认处于 paused 态;

2、添加 data 事件监听,它就变为 flowing 态;

3、删掉 data 事件监听,它就变为 paused 态;

4、pause() 可以将它变为 paused ;

5、resume() 可以将它变为 flowing 。

Writable Stream

  • drain 流干事件 1、调用 stream.write(chunk) 的时候,可能会得到 false ;

2、false 的意思是写太快了,数据积压了;

3、这个时候就不能再写 write 了,要监听 drain ;

4、等 drain 事件触发了,才能继续 write ;

const fs = require('fs')
const writer = fs.createWriteStream('./big_file.txt')
function writeOneMillionTimes(writer, data) {
  let i = 1000000;
  function write() {
    let ok = true;
    do {
      i--;
      if (i === 0) {
        // 最后一次!
        writer.write(data);
      } else {
        // 看看是应该继续,还是等待。
        // 不要传入回调,因为还没有完成。
        ok = writer.write(data);
        if(ok === false){
          console.log("不能再写了")
        }
      }
    } while (i > 0 && ok);
    if (i > 0) {
      // 必须早点停下来!
      // 等它排空时再写一些。
      writer.once('drain', ()=>{
        console.log("来点水")
        write()
      });
    }
  }
  write();
}
writeOneMillionTimes(writer,'hello world')
  • finish 事件 1、调用 stream.end() 之后;

2、而且缓冲区数据都已经传给底层系统之后;

3、触发 finish 事件。

创建自己的流,给别人用

创建一个 Writable Stream

stdin 会自动调用 write ,stdout 会自动调用 read

const {Writable} = require('stream')
const outStream = new Writable({
  write(chunk,encoding,callback){
  //传入什么,就输出什么
    console.log(chunk.toString())
    callback()
  }
})
process.stdin.pipe(outStream)

创建一个 Readable Stream

1、将所有数据都 push 进去,然后 pipe

const {Readable} = require('stream')
const inStream = new Readable()

inStream.push('abcdefg')
inStream.push('hijklmn')

inStream.pipe(process.stdout)

2、数据按需供给,对方调用 read ,才会给一次数据

const inStream = new Readable({
  read(){
    const char = String.fromCharCode(this.currentCharCode++))
    this.push(char)
    console.log(`推了 ${char}`)
    this.push(null)
  }
})
inStream.currentCharCode = 65
inStream.pipe(process.stdout)

创建一个 Duplex Stream

const {Duplex} = require('stream')
const inoutStream = new Duplex({
  write(chunk,encoding,callback){
    console.log(chunk.toString())
    callback()
  },
  read(){
    const char = String.fromCharCode(this.currentCharCode++)
    this.push(char)
    if(this.currentCharCode > 90){this.push(null)}
  }
})
inoutStream.currentCharCode = 65
process.stdin.pipe(inoutStream).pipe(process.stdout)

Transform Stream

const {Transform} = require('stream')
const upperCaseTr = new Transform({
  transform(chunk,encoding,callback){
  //将传入的数据变为大写
    this.push(chunk.toString().toUpperCase())
    callback()
  }
})
process.stdin.pipe(upperCaseTr).pipe(process.stdout)

压缩

const fs = require('fs')
const zlib = require('zlib')
const file = process.argv[2]
fs.createReadStream(file)
  .pipe(zlib.createGzip())
  .pipe(fs.createWriteStream(file + ".gz"))

//用 zlib 模块,将输入的文件压缩为 .gz 文件

转换文件,再压缩

const fs = require('fs')
const zlib = require('zlib')
const file = process.argv[2]
const {Transform} = require('stream')
const reportProgress = new Transform({
  transform(chunk,encoding,callback){
  //每传一次数据,就显示一个点。可以查看传了几次数据
  //这里的代码变下,就可以无限操作数据,类似 webpack 的 loader
    process.stdout.write('.')
    callback(null,chunk)
  }
})

fs.createReadStream(file)
  .pipe(zlib.createGzip())
  .pipe(reportProgress)
  .pipe(fs.createWriteStream(file + '.gz'))
  .on('finish',()=>console.log('done'))

加密压缩

const crypto = require('crypto')
...
fs.createReadStream(file)
  //加密方式为 aes192,密钥为 123456
  .pipe(crypto.createCipher('aes192','123456'))
  .pipe(zlib.createGzip())
  .pipe(reportProgress)
  .pipi(fs.createWriteStream(file + '.gz'))
  .on('finish',()=>console.log('done'))
//先加密,再压缩,不能先压缩再加密。

Stream 的用途

image.png

数据流中的积压问题