Rumah > hujung hadapan web > Soal Jawab bahagian hadapan > Terdapat beberapa jenis aliran dalam nod

Terdapat beberapa jenis aliran dalam nod

青灯夜游
Lepaskan: 2022-07-12 19:42:52
asal
2197 orang telah melayarinya

Strim nod mempunyai 4 jenis: 1. Boleh dibaca (strim boleh dibaca). Kaedah "_read" perlu dilaksanakan untuk mengembalikan kandungan; 2. Boleh tulis (strim boleh tulis), kaedah "_write" perlu dilaksanakan untuk menerima kandungan 3. Dupleks (strim boleh dibaca dan boleh ditulis), "_read" dan " Kaedah _write" perlu dilaksanakan Untuk menerima dan mengembalikan kandungan; 4. Transform (strim penukaran), anda perlu melaksanakan kaedah "_transform" untuk menukar kandungan yang diterima dan mengembalikan kandungan.

Terdapat beberapa jenis aliran dalam nod

Persekitaran pengendalian tutorial ini: sistem Windows 7, nodejs versi 16, komputer DELL G3.

Strim ialah konsep yang sangat asas dalam Nodejs Banyak modul asas dilaksanakan berdasarkan aliran dan memainkan peranan yang sangat penting. Pada masa yang sama, aliran juga merupakan konsep yang sangat sukar untuk difahami Ini terutamanya disebabkan oleh kekurangan dokumentasi yang relevan Bagi pemula NodeJ, ia sering mengambil banyak masa untuk memahami aliran sebelum mereka benar-benar dapat menguasai konsep ini. bagi kebanyakan NodeJ, ia Bagi pengguna, ia hanya digunakan untuk membangunkan aplikasi Web Pemahaman yang tidak mencukupi tentang aliran tidak menjejaskan penggunaannya. Walau bagaimanapun, pemahaman aliran boleh membawa kepada pemahaman yang lebih baik tentang modul lain dalam NodeJs, dan dalam beberapa kes, menggunakan strim untuk memproses data akan memberikan hasil yang lebih baik.

Strim

Strim ialah antara muka abstrak untuk memproses data penstriman dalam Node.js. Strim bukan antara muka sebenar, tetapi istilah umum untuk semua strim. Antara muka sebenar ialah ReadableStream, WritableStream dan ReadWriteStream.

interface ReadableStream extends EventEmitter {
    readable: boolean;
    read(size?: number): string | Buffer;
    setEncoding(encoding: BufferEncoding): this;
    pause(): this;
    resume(): this;
    isPaused(): boolean;
    pipe<T extends WritableStream>(destination: T, options?: { end?: boolean | undefined; }): T;
    unpipe(destination?: WritableStream): this;
    unshift(chunk: string | Uint8Array, encoding?: BufferEncoding): void;
    wrap(oldStream: ReadableStream): this;
    [Symbol.asyncIterator](): AsyncIterableIterator<string | Buffer>;
}

interface WritableStream extends EventEmitter {
    writable: boolean;
    write(buffer: Uint8Array | string, cb?: (err?: Error | null) => void): boolean;
    write(str: string, encoding?: BufferEncoding, cb?: (err?: Error | null) => void): boolean;
    end(cb?: () => void): this;
    end(data: string | Uint8Array, cb?: () => void): this;
    end(str: string, encoding?: BufferEncoding, cb?: () => void): this;
}

interface ReadWriteStream extends ReadableStream, WritableStream { }
Salin selepas log masuk

Dapat dilihat bahawa ReadableStream dan WritableStream adalah kedua-dua antara muka yang mewarisi kelas EventEmitter (antara muka dalam ts boleh mewarisi kelas, kerana ia hanya jenis penggabungan).

Kelas pelaksanaan yang sepadan dengan antara muka di atas ialah Boleh Dibaca, Boleh Ditulis dan Dupleks masing-masing

Terdapat 4 jenis strim dalam NodeJs:

  • Strim boleh dibaca boleh dibaca (melaksanakan ReadableStream)

  • Strim boleh ditulis boleh ditulis (melaksanakan Strim Boleh Tulis)

  • Strim boleh dibaca dan boleh ditulis dupleks (inher WritableStream selepas Boleh Dibaca)

  • Ubah aliran penukaran (warisi Dupleks)

Semuanya mempunyai kaedah untuk dilaksanakan:

  • Boleh dibaca perlu melaksanakan kaedah _baca untuk mengembalikan kandungan

  • Boleh tulis perlu melaksanakan kaedah _tulis untuk menerima kandungan

  • Keperluan Dupleks Laksanakan kaedah _baca dan _tulis untuk menerima dan mengembalikan kandungan

  • Transformasi Anda perlu melaksanakan kaedah _transform untuk menukar kandungan yang diterima dan mengembalikan

Boleh dibaca

Boleh dibaca ialah jenis strim Ia mempunyai dua mod dan tiga keadaan

Dua mod baca:

  • Mod aliran. Data akan dibaca dan ditulis daripada sistem asas kepada penimbal Apabila penimbal penuh, data akan dihantar secara automatik kepada pengendali acara berdaftar melalui EventEmitter secepat mungkin

  • Jeda. mod: Dalam mod ini, EventEmitter tidak akan dicetuskan secara aktif untuk menghantar data Kaedah Readable.read() mesti dipanggil secara eksplisit untuk membaca data daripada pembacaan penimbal akan mencetuskan respons kepada acara EventEmitter.

Tiga keadaan:

  • readableFlowing === null (keadaan awal)

  • readableFlowing = == palsu (mod jeda)

  • readableFlowing === benar (mod mengalir)

Alir awal readable.readableFlowing ialah null

Ia menjadi benar selepas menambahkan peristiwa data. Apabila pause(), unpipe() dipanggil, atau tekanan belakang diterima atau acara readable ditambah, readableFlowing akan ditetapkan kepada palsu Dalam keadaan ini, mengikat pendengar kepada peristiwa data tidak akan dayakan readableFlowing Tukar kepada benar.

Memanggil resume() menukar aliran boleh dibaca readableFlowing kepada benar

Mengalih keluar semua acara boleh dibaca ialah satu-satunya cara untuk menjadikan readableFlowing null.

事件名说明
readable当缓冲区有新的可读取数据时触发(每一个想缓存池插入节点都会触发)
data每一次消费数据后都会触发,参数是本次消费的数据
close流关闭时触发
error流发生错误时触发
方法名说明
read(size)消费长度为size的数据,返回null表示当前数据不足size,否则返回本次消费的数据。size不传递时表示消费缓存池中所有数据
const fs = require(&#39;fs&#39;);

const readStreams = fs.createReadStream(&#39;./EventEmitter.js&#39;, {
    highWaterMark: 100// 缓存池浮标值
})

readStreams.on(&#39;readable&#39;, () => {
    console.log(&#39;缓冲区满了&#39;)
    readStreams.read()// 消费缓存池的所有数据,返回结果并且触发data事件
})


readStreams.on(&#39;data&#39;, (data) => {
    console.log(&#39;data&#39;)
})
Salin selepas log masuk

https://github1s.com/nodejs/node/blob/v16.14.0/lib/internal/streams/readable.js#L527

当 size 为 0 会触发 readable 事件。

当缓存池中的数据长度达到浮标值 highWaterMark 后,就不会在主动请求生产数据,而是等待数据被消费后在生产数据

暂停状态的流如果不调用 read 来消费数据时,后续也不会在触发 datareadable,当调用 read 消费时会先判断本次消费后剩余的数据长度是否低于 浮标值,如果低于 浮标值 就会在消费前请求生产数据。这样在 read 后的逻辑执行完成后新的数据大概率也已经生产完成,然后再次触发 readable,这种提前生产下一次消费的数据存放在缓存池的机制也是缓存流为什么快的原因

流动状态下的流有两种情况

  • 生产速度慢于消费速度时:这种情况下每一个生产数据后一般缓存池中都不会有剩余数据,直接将本次生产的数据传递给 data 事件即可(因为没有进入缓存池,所以也不用调用 read 来消费),然后立即开始生产新数据,待上一次数据消费完后新数据才生产好,再次触发 data ,一只到流结束。
  • 生产速度快于消费速度时:此时每一次生产完数据后一般缓存池都还存在未消费的数据,这种情况一般会在消费数据时开始生产下一次消费的数据,待旧数据消费完后新数据已经生产完并且放入缓存池

他们的区别仅仅在于数据生产后缓存池是否还存在数据,如果存在数据则将生产的数据 push 到缓存池等待消费,如果不存在则直接将数据交给 data 而不加入缓存池。

值得注意的是当一个缓存池中存在数据的流从暂停模式进入的流动模式时,会先循环调用 read 来消费数据只到返回 null

暂停模式

Terdapat beberapa jenis aliran dalam nod

暂停模式下,一个可读流读创建时,模式是暂停模式,创建后会自动调用 _read 方法,把数据从数据源 push 到缓冲池中,直到缓冲池中的数据达到了浮标值。每当数据到达浮标值时,可读流会触发一个 " readable " 事件,告诉消费者有数据已经准备好了,可以继续消费。

一般来说, &#39;readable&#39; 事件表明流有新的动态:要么有新的数据,要么到达流的尽头。所以,数据源的数据被读完前,也会触发一次 &#39;readable&#39; 事件;

消费者 " readable " 事件的处理函数中,通过 stream.read(size) 主动消费缓冲池中的数据。

const { Readable } = require(&#39;stream&#39;)

let count = 1000
const myReadable = new Readable({
    highWaterMark: 300,
    // 参数的 read 方法会作为流的 _read 方法,用于获取源数据
    read(size) {
        // 假设我们的源数据上 1000 个1
        let chunk = null
        // 读取数据的过程一般是异步的,例如IO操作
        setTimeout(() => {
            if (count > 0) {
                let chunkLength = Math.min(count, size)
                chunk = &#39;1&#39;.repeat(chunkLength)
                count -= chunkLength
            }
            this.push(chunk)
        }, 500)
    }
})
// 每一次成功 push 数据到缓存池后都会触发 readable
myReadable.on(&#39;readable&#39;, () => {
    const chunk = myReadable.read()//消费当前缓存池中所有数据
    console.log(chunk.toString())
})
Salin selepas log masuk

值得注意的是, 如果 read(size) 的 size 大于浮标值,会重新计算新的浮标值,新浮标值是size的下一个二次幂(size <= 2^n,n取最小值)

//  hwm 不会大于 1GB.
const MAX_HWM = 0x40000000;
function computeNewHighWaterMark(n) {
  if (n >= MAX_HWM) {
    // 1GB限制
    n = MAX_HWM;
  } else {
    //取下一个2最高幂,以防止过度增加hwm
    n--;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    n++;
  }
  return n;
}
Salin selepas log masuk

流动模式

Terdapat beberapa jenis aliran dalam nod

所有可读流开始的时候都是暂停模式,可以通过以下方法可以切换至流动模式:

  • 添加 " data " 事件句柄;
  • 调用 “ resume ”方法;
  • 使用 " pipe " 方法把数据发送到可写流

流动模式下,缓冲池里面的数据会自动输出到消费端进行消费,同时,每次输出数据后,会自动回调 _read 方法,把数据源的数据放到缓冲池中,如果此时缓存池中不存在数据则会直接吧数据传递给 data 事件,不会经过缓存池;直到流动模式切换至其他暂停模式,或者数据源的数据被读取完了( push(null) );

可读流可以通过以下方式切换回暂停模式:

  • 如果没有管道目标,则调用 stream.pause()
  • 如果有管道目标,则移除所有管道目标。调用 stream.unpipe() 可以移除多个管道目标。
const { Readable } = require(&#39;stream&#39;)

let count = 1000
const myReadable = new Readable({
    highWaterMark: 300,
    read(size) {
        let chunk = null
        setTimeout(() => {
            if (count > 0) {
                let chunkLength = Math.min(count, size)
                chunk = &#39;1&#39;.repeat(chunkLength)
                count -= chunkLength
            }
            this.push(chunk)
        }, 500)
    }
})

myReadable.on(&#39;data&#39;, data => {
    console.log(data.toString())
})
Salin selepas log masuk

Writable

相对可读流来说,可写流要简单一些。

Terdapat beberapa jenis aliran dalam nod

当生产者调用 write(chunk) 时,内部会根据一些状态(corked,writing等)选择是否缓存到缓冲队列中或者调用 _write,每次写完数据后,会尝试清空缓存队列中的数据。如果缓冲队列中的数据大小超出了浮标值(highWaterMark),消费者调用 write(chunk) 后会返回 false,这时候生产者应该停止继续写入。

那么什么时候可以继续写入呢?当缓冲中的数据都被成功 _write 之后,清空了缓冲队列后会触发 drain 事件,这时候生产者可以继续写入数据。

当生产者需要结束写入数据时,需要调用 stream.end 方法通知可写流结束。

const { Writable, Duplex } = require(&#39;stream&#39;)
let fileContent = &#39;&#39;
const myWritable = new Writable({
    highWaterMark: 10,
    write(chunk, encoding, callback) {// 会作为_write方法
        setTimeout(()=>{
            fileContent += chunk
            callback()// 写入结束后调用
        }, 500)
    }
})

myWritable.on(&#39;close&#39;, ()=>{
    console.log(&#39;close&#39;, fileContent)
})
myWritable.write(&#39;123123&#39;)// true
myWritable.write(&#39;123123&#39;)// false
myWritable.end()
Salin selepas log masuk

注意,在缓存池中数据到达浮标值后,此时缓存池中可能存在多个节点,在清空缓存池的过程中(循环调用_read),并不会向可读流一样尽量一次消费长度为浮标值的数据,而是每次消费一个缓冲区节点,即使这个缓冲区长度于浮标值不一致也是如此

const { Writable } = require(&#39;stream&#39;)


let fileContent = &#39;&#39;
const myWritable = new Writable({
    highWaterMark: 10,
    write(chunk, encoding, callback) {
        setTimeout(()=>{
            fileContent += chunk
            console.log(&#39;消费&#39;, chunk.toString())
            callback()// 写入结束后调用
        }, 100)
    }
})

myWritable.on(&#39;close&#39;, ()=>{
    console.log(&#39;close&#39;, fileContent)
})

let count = 0
function productionData(){
    let flag = true
    while (count <= 20 && flag){
        flag = myWritable.write(count.toString())
        count++
    }
    if(count > 20){
        myWritable.end()
    }
}
productionData()
myWritable.on(&#39;drain&#39;, productionData)
Salin selepas log masuk

上述是一个浮标值为 10 的可写流,现在数据源是一个 0——20 到连续的数字字符串,productionData 用于写入数据。

  • 首先第一次调用 myWritable.write("0") 时,因为缓存池不存在数据,所以 "0" 不进入缓存池,而是直接交给 _wirtemyWritable.write("0") 返回值为 true

  • 当执行 myWritable.write("1") 时,因为 _wirtecallback 还未调用,表明上一次数据还未写入完,位置保证数据写入的有序性,只能创建一个缓冲区将 "1" 加入缓存池中。后面 2-9 都是如此

  • 当执行 myWritable.write("10") 时,此时缓冲区长度为 9(1-9),还未到达浮标值, "10" 继续作为一个缓冲区加入缓存池中,此时缓存池长度变为 11,所以 myWritable.write("1") 返回 false,这意味着缓冲区的数据已经足够,我们需要等待 drain 事件通知时再生产数据。

  • 100ms过后,_write("0", encoding, callback)callback 被调用,表明 "0" 已经写入完成。然后会检查缓存池中是否存在数据,如果存在则会先调用 _read 消费缓存池的头节点("1"),然后继续重复这个过程直到缓存池为空后触发 drain 事件,再次执行 productionData

  • 调用 myWritable.write("11"),触发第1步开始的过程,直到流结束。

Duplex

在理解了可读流与可写流后,双工流就好理解了,双工流事实上是继承了可读流然后实现了可写流(源码是这么写的,但是应该说是同时实现了可读流和可写流更加好)。

Terdapat beberapa jenis aliran dalam nod

Duplex 流需要同时实现下面两个方法

  • 实现 _read() 方法,为可读流生产数据

  • 实现 _write() 方法,为可写流消费数据

上面两个方法如何实现在上面可写流可读流的部分已经介绍过了,这里需要注意的是,双工流是存在两个独立的缓存池分别提供给两个流,他们的数据源也不一样

以 NodeJs 的标准输入输出流为例:

  • 当我们在控制台输入数据时会触发其 data 事件,这证明他有可读流的功能,每一次用户键入回车相当于调用可读的 push 方法推送生产的数据。
  • 当我们调用其 write 方法时也可以向控制台输出内容,但是不会触发 data 事件,这说明他有可写流的功能,而且有独立的缓冲区,_write 方法的实现内容就是让控制台展示文字。
// 每当用户在控制台输入数据(_read),就会触发data事件,这是可读流的特性
process.stdin.on(&#39;data&#39;, data=>{
    process.stdin.write(data);
})

// 每隔一秒向标准输入流生产数据(这是可写流的特性,会直接输出到控制台上),不会触发data
setInterval(()=>{
    process.stdin.write(&#39;不是用户控制台输入的数据&#39;)
}, 1000)
Salin selepas log masuk

Transform

Terdapat beberapa jenis aliran dalam nod

可以将 Duplex 流视为具有可写流的可读流。两者都是独立的,每个都有独立的内部缓冲区。读写事件独立发生。

                             Duplex Stream
                          ------------------|
                    Read  <-----               External Source
            You           ------------------|  
                    Write ----->               External Sink
                          ------------------|
Salin selepas log masuk

Transform 流是双工的,其中读写以因果关系进行。双工流的端点通过某种转换链接。读取要求发生写入。

                                 Transform Stream
                           --------------|--------------
            You     Write  ---->                   ---->  Read  You
                           --------------|--------------
Salin selepas log masuk

对于创建 Transform 流,最重要的是要实现 _transform 方法而不是 _write 或者 _read_transform 中对可写流写入的数据做处理(消费)然后为可读流生产数据。

转换流还经常会实现一个 `_flush` 方法,他会在流结束前被调用,一般用于对流的末尾追加一些东西,例如压缩文件时的一些压缩信息就是在这里加上的
Salin selepas log masuk
const { write } = require(&#39;fs&#39;)
const { Transform, PassThrough } = require(&#39;stream&#39;)

const reurce = &#39;1312123213124341234213423428354816273513461891468186499126412&#39;

const transform = new Transform({
    highWaterMark: 10,
    transform(chunk ,encoding, callback){// 转换数据,调用push将转换结果加入缓存池
        this.push(chunk.toString().replace(&#39;1&#39;, &#39;@&#39;))
        callback()
    },
    flush(callback){// end触发前执行
        this.push(&#39;<<<&#39;)
        callback()
    }
})


// write 不断写入数据
let count = 0
transform.write(&#39;>>>&#39;)
function productionData() {
    let flag = true
    while (count <= 20 && flag) {
        flag = transform.write(count.toString())
        count++
    }
    if (count > 20) {
        transform.end()
    }
}
productionData()
transform.on(&#39;drain&#39;, productionData)


let result = &#39;&#39;
transform.on(&#39;data&#39;, data=>{
    result += data.toString()
})
transform.on(&#39;end&#39;, ()=>{
    console.log(result)
    // >>>0@23456789@0@1@2@3@4@5@6@7@8@920<<<
})
Salin selepas log masuk

更多node相关知识,请访问:nodejs 教程

Atas ialah kandungan terperinci Terdapat beberapa jenis aliran dalam nod. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:php.cn
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan