Node.js高级编程【二】核心模块

目录

二、核心模块

21、 Nodejs事件环理解

​22、 Nodejs与浏览器事件环区别

23、Nodejs事件环常见问题 

24、核心模块之stream

25、stream之可读流

26、stream之可写流

27、stream之双工和转换流

28、 文件可读流创建和消费

29、文件可读流事件与应用

30、 文件可写流

31、write执行流程

32、控制写入速度

32、背压机制

33、 模拟文件可读流01

34、链表结构

35、 单向链表实现

36、单向链表实现队列

37、文件可写流实现

38、pipe方法使用

二、核心模块

 21、 Nodejs事件环理解

setTimeout(() => {
    console.log('s1');
    Promise.resolve().then(() => {
        console.log('p1');
    })
    Promise.resolve().then(() => {
        console.log('t1');
    })
});

Promise.resolve().then(() => {
    console.log('p2');
})

console.log('start');

setTimeout(() => {
    console.log('s2');   
    Promise.resolve().then(() => {
        console.log('p3');
    })
    Promise.resolve().then(() => {
        console.log('t2');
    })
});

console.log('end');

// start end p2 s1 p1 t1 s2 p3 t2

 22、 Nodejs与浏览器事件环区别

(1)任务队列数不同

  • 浏览器中只有二个任务队列
  • Nodejs 中有6个事件队列

(2)Nodejs 微任务执行实际不同

  • 二者都会在同步代码执行完毕后执行微任务
  • 浏览器平台下每当一个宏任务执行完毕后就清空微任务
  • Nodejs 平台在事件队列切换时会去清空微任务

(3)微任务优先级不同

  • 浏览器事件环中,微任务存放于事件队列,先进先出
  • Nodejs 中 process.nextTick 先于 promise.then

23、Nodejs事件环常见问题 

(1)setTimeout 和 setImmediate同步执行顺序是随机的

setTimeout(() => {
    console.log('timeout');
});

setImmediate(() => {
    console.log('immediate');
})

// 会出现两种结果
// 第一种
// timeout
// immediate

// 第二种
// immediate
// timeout

 

原因:是因为 setTimeout后面要跟一个时间,没写的情况下默认是0,不管是在Node和浏览器的平台下,都会有一个不稳定的因素,有些情况下会产生一些延时,延时的话,代码从上往下,会先加载 setImmediate,后加载 setTimeout;没有延时,就是先加载 setTimeout,后加载  setImmediate

解决办法:放在 I/O 回调函数,就不会出现这样的情况


const fs = require('fs')

fs.readFile('./m1.js', () => {
    setTimeout(() => {
        console.log('timeout');
    }, 0);
    
    setImmediate(() => {
        console.log('immediate');
    })
})

// immediate
// timeout

 24、核心模块之stream

(1) 将左边的数据写到右边 js 文件中

(2)Node.js 诞生之初就是为了提高 IO 性能

(3)文件操作系统和网络模块实现了流接口

(4)Node.js 中的流就是处理流式数据的抽象接口

(5)应用程序中为什么使用流来处理数据?

原来方式常见问题:

  • 同步读取资源文件,用户需要等待数据读取完成
  • 资源文件最终一次性加载至内存,开销较大

现在方式

流处理数据的优势

  • 时间效率:流的分段处理可以同时操作多个数据 chunk
  • 空间效率:同一时间流无须占据大内存空间
  • 使用方便:流配合管理,扩展程序变得简单,

(6)Node.js 内置了 stream,它实现了流操作对象

(7)Node.js 中流的分类

  • Readable:可读流,能够实现数据的读取
  • Writeable:可写流,能够实现数据的写操作
  • Duplex:双工流,即可读又可写
  • Tranform:转换流,可读可写,还能实现数据转换

(8)Node.js 流特点

  • Stream 模块实现了四个具体的抽象
  • 所有流都继承自 EventEmitter

25、stream之可读流

(1)生产供程序消费数据的流

 (2)如何自定义可读流?

  • 继承 stream 里的 Readable
  • 重写 _read 方法调用 push 产生数据
const { Readable } = require('stream')

// 模拟底层数据
let source = ['lg', 'zce', 'syy']

// 自定义类继承 Readable
class MyReadable extends Readable {
    constructor(source) {
        super()
        this.source = source
    }
    _read() {
        let data = this.source.shift() || null 
        this.push(data)
    }
}

// 实例化
let myReadable = new MyReadable(source)

// myReadable.on('readable', () => {
//     let data = null
//     while((data = myReadable.read(2)) !== null) {
//         console.log(data.toString());
//         // lg
//         // zc
//         // es
//         // yy
//     }
// })

myReadable.on('data', (data) => {
    console.log(data.toString());
    // lg
    // zce
    // syy
})

(3)自定义可读流问题

  • 底层数据读取完成之后如何处理?
    • 数据传递完之后,传入一个 null 
  • 消费者如何获取可读流中的数据?
    • readable 事件:当流中存在可读取数据时触发
    • data 事件:当流中数据块传给消费者厚触发
  • 消费数据为什么存在二种方式?
    • 满足不同的使用场景
    • 流动模式
    • 暂停模式
    • 两者区别在于消费数据是否需要主动调用 readable 来读取数据

(4)可读流总结

  • 明确数据生产与消费流程
  • 利用 API 实现自定义的可读流
  • 明确数据消费的事件使用 

26、stream之可写流

(1)用于消费数据的流

(2)自定义可写流

  • 继承 stream 模块的 Writeable
  • 重写 _write 方法,调用 write 执行写入

(3)可写流事件

  • pipe 事件:可读流调用 pipe() 方法时触发
  • unpipe 事件::可读流调用 unpipe 方法时触发
const { Writable } = require('stream')

class MyWriteable extends Writable {
    constructor() {
        super()
    }
    _write(chunk, en, done) {
        process.stdout.write(chunk.toString() + '<----')
        process.nextTick(done)
    }
}

let myWriteable = new MyWriteable()

myWriteable.write('拉钩教育', 'utf-8', () => {
    console.log('end');
})

// 拉钩教育<----end

27、stream之双工和转换流

(1)可读、可写、双工、转换是单一抽象具体实现

(2)Node.js 诞生的初衷就是解决密集型 IO 事务

(3)Node.js 中处理数据模块继承了流和 EventEmitter

(4)stream、四种类型流、实现流操作的模块

(5)Duplex 是双工流,技能生产又能消费

(6)自定义双工流

  • 继承 Duplex
  • 重写 _read 方法,调用 push 生产数据
  • 重写 _write 方法,调用 write 消费数据
let { Duplex } = require('stream')

class MyDuplex extends Duplex {
    constructor(source) {
        super()
        this.source = source
    }
    _read() {
        let data = this.source.shift() || null
        this.push(data)
    }
    _write(chunk, en, next) {
        process.stdout.write(chunk)
        process.nextTick(next)
    }
}

let source = ['a', 'b', 'c']
let myDuplex = new MyDuplex(source)

// myDuplex.on('data', (chunk) => {
//     console.log(chunk.toString());
// })

// a
// b
// c

myDuplex.write('拉钩教育', () => {
    console.log(111);
})

// 拉钩教育111

(7)Transform 也是一个双工流

它和 Duplex 的区别是,Duplex的读和写是独立的

自定义转换流

  • 继承 Transform 类
  • 重写 _transfrm 方法,调用 push 和 callback 
  • 重写 _flush 方法,处理剩余数据
let { Transform } = require('stream')

class MyTransform extends Transform {
    constructor() {
        super()
    }
    _transform(chunk, en, cb) {
        this.push(chunk.toString().toUpperCase())
        cb(null)
    }
}

let t = new MyTransform()

t.write('a')

t.on('data', (chunk) => {
    console.log(chunk.toString());
})

// A

(8)Node.js 中的四种流

  • Readable 可读流
  • Writeable 可写流
  • Duplex 双工流
  • Trnsform 转换流

28、 文件可读流创建和消费

const fs = require('fs')

let rs = fs.createReadStream('text.txt', {
    flags: 'r', // 文件流以什么样的方式打开或者操作 r 表示 read 可读
    encoding: null, // 对编码进行设置,默认 null 返回的是 Buffer 数据类型
    fd: null, // 文件标识符,默认值从3开始的,0,1,2,是标准输入、输出和错误占用
    mode: 438, // 权限类,十进制是438,八进制是0O66
    autoClose: true, // 是否是自动关闭文件
    start: 0, // 当前从底层的哪个位置读取,默认是 0 
    // end: 3, // 到哪个位置结束读取
    highWaterMark: 2 // 水位线,表示每次要读取多少个字节的数据
})

// rs.on('data', (chunk) => {
//     console.log(chunk.toString());
//     rs.pause()
//     setTimeout(() => {
//         rs.resume()
//     }, 1000)
// })

// 01 23 45 67 89

rs.on('readable', () => {
    // let data = rs.read()
    // console.log(data);
    // <Buffer 30 31>
    // <Buffer 32 33>
    // <Buffer 34 35>
    // <Buffer 36 37>
    // <Buffer 38 39>

    // let data
    // while((data = rs.read()) !== null) {
    //     console.log(data.toString());
    //     // 01 23 45 67 89
    // }

    let data
    while((data = rs.read(1)) !== null) {
        console.log(data.toString());
        console.log('------', rs._readableState.length);
        // 0
        // ------ 1
        // 1
        // ------ 0
        // 2
        // ------ 1
        // 3
        // ------ 0
        // 4
        // ------ 1
        // 5
        // ------ 0
        // 6
        // ------ 1
        // 7
        // ------ 0
        // 8
        // ------ 1
        // 9
        // ------ 0
    }
})


29、文件可读流事件与应用

const fs = require('fs')

let rs = fs.createReadStream('text.txt', {
    flags: 'r', // 文件流以什么样的方式打开或者操作 r 表示 read 可读
    encoding: null, // 对编码进行设置,默认 null 返回的是 Buffer 数据类型
    fd: null, // 文件标识符,默认值从3开始的,0,1,2,是标准输入、输出和错误占用
    mode: 438, // 权限类,十进制是438,八进制是0O66
    autoClose: true, // 是否是自动关闭文件
    start: 0, // 当前从底层的哪个位置读取,默认是 0 
    // end: 3, // 到哪个位置结束读取
    highWaterMark: 2 // 水位线,表示每次要读取多少个字节的数据
})

rs.on('open', (fd) => {
    console.log(fd, '文件打开了');
})

rs.on('close', () => {
    console.log('文件关闭了');
})

let bufferArr = []
rs.on('data', (chunk) => {
    bufferArr.push(chunk)
})

rs.on('end', () => {
    console.log(Buffer.concat(bufferArr).toString())
    console.log('当数据被清空之后');
})

rs.on('error', (err) => {
    console.log('出错了');
})

// 3 文件打开了
// 0123456789
// 当数据被清空之后
// 文件关闭了

注意: end 事件在 close 事件之前结束

30、 文件可写流

const fs = require('fs')

const ws = fs.createWriteStream('test.txt', {
    flags: 'w',
    mode: 438,
    fd: null,
    encoding: 'utf-8',
    start: 0,
    highWaterMark: 3
})

let buf = Buffer.from('abc')

// buf 类型是 字符串 或者 buffer ==> fs rs
// ws.write(buf, ()=> {
//     console.log('ok2');
// })

// ws.write('拉钩教育', () => {
//     console.log('ok1');
// })

ws.on('open', (fd) => {
    console.log('open', fd); // open 3
})

ws.write('2')

// close 是在数据写入操作全部完成之后再执行
ws.on('close', () => {
    console.log('文件关闭了');
})

// end 执行之后就意味着数据写入操作完成
ws.end('拉钩教育')

// error
ws.on('error', (err) => {
    console.log('出错了');
})

31、write执行流程

const fs = require('fs')

let ws = fs.createWriteStream('test.txt', {
    highWaterMark: 3
})

let flag = ws.write('1')
console.log(flag)
// true

flag = ws.write('2')
console.log(flag)
// true

// 如果 flag 为 false 并不是说明当前数据不能被执行写入
flag = ws.write('3')
console.log(flag)
// false

// flag 为 false 会执行drain,否则不执行
ws.on('drain', () => {
    console.log('11');
})
// 11

/**
 * 01 第一次调用 write 方法时是将数据直接写入到文件中
 * 02 第二次开始 write 方法就是将数据写入至缓存中
 * 03 生产速度 和 消费速度 是不一样的,一般情况下生产速度要比消费速度快很多
 * 04 当 flag 为 false 之后并不意味着当前次的数据不能被写入了,但是我们应该告知数据的生产者,当前的消费速度已经跟不上 生产速度了。所以这个时候,一版我们会将可读流的模块修改为在听模式
 * 05 当数据生产者暂停之后,消费者会慢慢的消化它内部缓存中的数据,直接可以再次被执行写入操作
 * 06 当缓冲区可以继续写入数据时如何让生产者知道? drain 事件
 */

 32、控制写入速度

  •  一次性写入
  • 分批写入
/**
 * 需求: ‘拉钩教育’写入指定的文件
 * 01 一次性写入
 * 02 分批写入
 * 对比
 */
let fs = require('fs')

let ws = fs.createWriteStream('test.txt', {
    highWaterMark: 3
})

// 一次性写入
// ws.write('拉钩教育')

// 分批写入
let  source = "拉钩教育".split('')
let num = 0
let flag = true

function executeWrite() {
    flag = true
    while(num !== 4 && flag) {
        flag = ws.write(source[num])
        num ++
    }
}
executeWrite()

ws.on('drain', () => {
    console.log('drain 执行了');
    executeWrite()
})

// drain 执行了
// drain 执行了
// drain 执行了
// drain 执行了

32、背压机制

(1)Node.js 的 stream 已实现了背压机制

(2)如果不进行数据的背压,就会出现内存溢出、GC频繁调用、其他进程变慢

 

let fs = require('fs')

let rs = fs.createReadStream('text.txt', {
    highWaterMark: 4
})

let ws = fs.createWriteStream('textDemo.txt', {
    highWaterMark: 1
})

// let flag = true

// rs.on('data', (chunk) => {
//     flag = ws.write(chunk, () => {
//         console.log('写完了');
//     })
//     if (!flag) {
//         rs.pause()
//     }
// })

// ws.on('drain', () => {
//     rs.resume()
// })

// 或者 用 Pipe 方式写
rs.pipe(ws)

33、 模拟文件可读流01

const fs = require('fs')
const EventEmitter = require('events')

class MyFileReadStream extends EventEmitter{
  constructor(path, options = {}) {
    super()
    this.path = path
    this.flags = options.flags || "r"
    this.mode = options.mode || 438
    this.autoClose = options.autoClose || true 
    this.start = options.start || 0
    this.end = options.end 
    this.highWaterMark = options.highWaterMark || 64 * 1024 
    this.readOffset = 0

    this.open()

    this.on('newListener', (type) => {
      if (type === 'data') {
        this.read()
      }
    })
  }
  open() {
    // 原生 open 方法来打开指定位置上的文件
    fs.open(this.path, this.flags, this.mode, (err, fd) => {
      if (err) {
        this.emit('error', err)
      }
      this.fd = fd
      this.emit('open', fd)
    })
  }
  read() {
    if (typeof this.fd !== 'number') {
      return this.once('open', this.read)
    }

    let buf = Buffer.alloc(this.highWaterMark)

    let howMuchToRead
    /* if (this.end) {
      howMuchToRead = Math.min(this.end - this.readOffset + 1, this.highWaterMark)
    } else {
      howMuchToRead = this.highWaterMark
    } */

    howMuchToRead = this.end ? Math.min(this.end - this.readOffset + 1, this.highWaterMark) : this.highWaterMark

    fs.read(this.fd, buf, 0, howMuchToRead, this.readOffset, (err, readBytes) => {
      if (readBytes) {
        this.readOffset += readBytes
        this.emit('data', buf.slice(0, readBytes))
        this.read()
      } else {
        this.emit('end')
        this.close()
      }
    })
  }
  close() {
    fs.close(this.fd, () => {
      this.emit('close')
    })
  }
}

let rs = new MyFileReadStream('test.txt', {
  end: 7,
  highWaterMark: 3
})

rs.on('data', (chunk) => {
  console.log(chunk)
})

34、链表结构

(1)为什么不采用数组存储数据?

  • 数组存储数据的长度具有上限
  • 数组存在塌陷问题

(2)链表是一系列节点的集合

每个节点都具有指向下一个节点的属性

(3)链表分类

  • 双向链表
  • 单向链表

  • 循环链表

35、 单向链表实现

/**
 * 01 node + head + null
 * 02 head ---> null
 * 03 size
 * 04 next element
 * 05 增加 删除 修改 查询 清空
 */

class Node {
    constructor(element, next) {
        this.element = element
        this.next = next
    }
}

class LinkedList {
    constructor(head, size) {
        this.head = null
        this.size = 0
    }
    // 获取节点的位置
    _getNode(index) {
        if (index < 0 || index >= this.size) {
            throw new Error('越界了')
        }
        let currentNode = this.head
        for (let i = 0; i < index; i++) {
            currentNode = currentNode.next
        }
        return currentNode
    }
    // 增加
    add(index, element) {
        if (arguments.length === 1) {
            element = index
            index = this.size
        }
        if (index < 0 || index > this.size) {
            throw new Error('cross the border')
        }
        if (index === 0) {
            let head = this.head // 保存原有 head 的指向
            this.head = new Node(element, head)
        } else {
            let prevNode = this._getNode(index - 1)
            prevNode.next = new Node(element, prevNode.next)
        }
        this.size++
    }
    // 删除
    remove(index) {
        if (index === 0) {
            let head = this.head
            this.head = head.next
        } else {
            let prevNode = this._getNode(index -1)
            prevNode.next = prevNode.next.next
        }
        this.size--
    }
    // 修改
    set(index, element) {
        let node = this._getNode(index)
        node.element = element
    }
    // 查询
    get(index) {
        return this._getNode(index)
    }
    // 清空
    clear() {
        this.head = null
        this.size = 0
    }
}

const l1 = new LinkedList()
l1.add('node1')
l1.add('node2')
l1.add(1,'node3')
// console.log(l1);
// LinkedList { head: Node { element: 'node1', next: null }, size: 1 }

// l1.remove(1)
// console.log(l1);
// LinkedList {
//     head: Node {
//       element: 'node1',
//       next: Node { element: 'node2', next: null }
//     },
//     size: 2
//   }

l1.set(1, 'node-3-3')
// console.log(l1);
// LinkedList {
//     head: Node {
//       element: 'node1',
//       next: Node { element: 'node-3-3', next: [Node] }
//     },
//     size: 3
//   }

let a = l1.get(0)
// console.log(a);
// Node {
//     element: 'node1',
//     next: Node {
//       element: 'node-3-3',
//       next: Node { element: 'node2', next: null }
//     }
//   }

l1.clear()
console.log(l1);
// LinkedList { head: null, size: 0 }

36、单向链表实现队列

/**
 * 01 node + head + null
 * 02 head ---> null
 * 03 size
 * 04 next element
 * 05 增加 删除 修改 查询 清空
 */

class Node {
    constructor(element, next) {
        this.element = element
        this.next = next
    }
}

class LinkedList {
    constructor(head, size) {
        this.head = null
        this.size = 0
    }
    // 获取节点的位置
    _getNode(index) {
        if (index < 0 || index >= this.size) {
            throw new Error('越界了')
        }
        let currentNode = this.head
        for (let i = 0; i < index; i++) {
            currentNode = currentNode.next
        }
        return currentNode
    }
    // 增加
    add(index, element) {
        if (arguments.length === 1) {
            element = index
            index = this.size
        }
        if (index < 0 || index > this.size) {
            throw new Error('cross the border')
        }
        if (index === 0) {
            let head = this.head // 保存原有 head 的指向
            this.head = new Node(element, head)
        } else {
            let prevNode = this._getNode(index - 1)
            prevNode.next = new Node(element, prevNode.next)
        }
        this.size++
    }
    // 删除
    remove(index) {
        let rmNode = null
        if (index === 0) {
            rmNode = this.head
            if (!rmNode) {
                return undefined
            }
            this.head = rmNode.next
        } else {
            let prevNode = this._getNode(index -1)
            rmNode = prevNode.next
            prevNode.next = rmNode.next
        }
        this.size--
        return rmNode
    }
    // 修改
    set(index, element) {
        let node = this._getNode(index)
        node.element = element
    }
    // 查询
    get(index) {
        return this._getNode(index)
    }
    // 清空
    clear() {
        this.head = null
        this.size = 0
    }
}

class Queue {
    constructor() {
        this.LinkedList = new LinkedList()
    }
    enQueue(data) {
        this.LinkedList.add(data)
    }
    deQueue() {
        return this.LinkedList.remove(0)
    }
}

const q = new Queue()

q.enQueue('node1')
q.enQueue('node2')

let a = q.deQueue()
a = q.deQueue()
a = q.deQueue()
console.log(a);
// undefined

37、文件可写流实现

const fs = require('fs')
const EventsEmitter = require('events')
const Queue = require('./linkedlist')

class MyWriterStream extends EventsEmitter {
    constructor(path, options={}) {
        super()
        this.path = path
        this.flags = options.flags || 'w'
        this.mode = options.mode || 438
        this.autoClose = options.autoClose || true
        this.start = options.start || 0
        this.encoding = options.encoding || 'uft-8'
        this.highWaterMark = options.highWaterMark || 16*1024

        this.open()

        this.writeoffset = this.start
        this.writing = false
        this.writeLen = 0
        this.needDrain = false
        this.cache = new Queue()
    }
    open() {
        // 原生 fs.open
        fs.open(this.path, this.flags, (err, fd) => {
            if (err) {
                this.emit('error', err)
            }
            // 正常打开文件
            this.fd = fd
            this.emit('open', fd)
        })
    }
    write(chunk, encoding, cb) {
        chunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
    
        this.writeLen += chunk.length
        let flag = this.writeLen < this.highWaterMark
        this.needDrain = !flag

        if (this.writing) {
            // 当前正在执行写入,所以内容应该排队
            this.cache.enQueue({ chunk, encoding, cb})
        } else {
            this.writing = true
            // 当前不是正在写入,那么就执行写入
            this._write(chunk, encoding, () => {
                cb()
                // 清空排队的内容
                this._clearBuffer()
            })
        }
        return flag
    }
    _write(chunk, encoding, cb) {
        if (typeof this.fd !== 'number') {
            return this.once('open', () => {
                return this._write(chunk, encoding, cb)
            })
        }
        fs.write(this.fd, chunk, this.start, chunk.length, this.writeoffset, (err, written) => {
            this.writeoffset += written
            this.writeLen -= written

            cb && cb()
        })
    }
    _clearBuffer() {
        let data = this.cache.deQueue()
        if (data) {
            this.write(data.element.chunk, data.element.encoding, () => {
                data.element.cb && data.element.cb()
                this._clearBuffer()
            })
        } else {
            if (this.needDrain) {
                this.needDrain = false
                this.emit('drain')
            }
        }
    }
}

const ws = new MyWriterStream('./f9.txt', {
    highWaterMark: 1
})

ws.on('open', (fd) => {
    console.log('open--->', fd);
})

let flag = ws.write('1', 'utf8', () => {
    console.log('ok1');
})
// console.log(flag)

flag = ws.write('10', 'utf8', () => {
    console.log('ok1');
})
// console.log(flag)

ws.on('drain', () => {
    console.log('drain');
})

38、pipe方法使用

// ReadStream.js

const fs = require('fs')
const EventEmitter = require('events')

class MyReadStream extends EventEmitter{
  constructor(path, options = {}) {
    super()
    this.path = path
    this.flags = options.flags || "r"
    this.mode = options.mode || 438
    this.autoClose = options.autoClose || true 
    this.start = options.start || 0
    this.end = options.end 
    this.highWaterMark = options.highWaterMark || 64 * 1024 
    this.readOffset = 0

    this.open()

    this.on('newListener', (type) => {
      if (type === 'data') {
        this.read()
      }
    })
  }
  open() {
    // 原生 open 方法来打开指定位置上的文件
    fs.open(this.path, this.flags, this.mode, (err, fd) => {
      if (err) {
        this.emit('error', err)
      }
      this.fd = fd
      this.emit('open', fd)
    })
  }
  read() {
    if (typeof this.fd !== 'number') {
      return this.once('open', this.read)
    }

    let buf = Buffer.alloc(this.highWaterMark)

    let howMuchToRead
    /* if (this.end) {
      howMuchToRead = Math.min(this.end - this.readOffset + 1, this.highWaterMark)
    } else {
      howMuchToRead = this.highWaterMark
    } */

    howMuchToRead = this.end ? Math.min(this.end - this.readOffset + 1, this.highWaterMark) : this.highWaterMark

    fs.read(this.fd, buf, 0, howMuchToRead, this.readOffset, (err, readBytes) => {
      if (readBytes) {
        this.readOffset += readBytes
        this.emit('data', buf.slice(0, readBytes))
        this.read()
      } else {
        this.emit('end')
        this.close()
      }
    })
  }
  close() {
    fs.close(this.fd, () => {
      this.emit('close')
    })
  }
  pipe(ws) {
    this.on('data', (data) => {
      let flag = ws.write(data)
      console.log(flag);
      if (!flag) {
        this.pause()
      }
    })
    ws.on('drain', () => {
      this.resume()
    })
  }
}


module.exports = MyReadStream
const fs = require('fs')
const myReadStream = require('./ReadStream')

// const rs = fs.createReadStream('./f9.txt', {
//   highWaterMark: 4
// })

const rs = new myReadStream('./f9.txt')

const ws = fs.createWriteStream('./f10.txt')

rs.pipe(ws)

// data 
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值