10分钟闲聊Node.js中的流

106 阅读3分钟

概念

流是一组有序的,有起点和终点的字节数据传输手段,在Node.js中处理流式数据的抽象接口 它不关心文件的整体内容,只关注是否从文件中读到了数据,以及读到数据之后的处理

分类

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

可读流

概念

可读流被用来为 I/O 源提供灵活的 API,也可以被用作解析器:

  • 继承自 steam.Readable 类
  • 并实现一个 _read(size) 方法

示例

const stream = fs.createReadStream('./file.txt')
let chunks = []
stream.on("data"(chunk) => { // chunk是Buffer类型
    chunks.push(chunk)
})
stream.on("end"() => {
    constcontent = Buffer.concat(chunks).toString()
    console.log(content)
})
  1. data事件在传输过程中触发
  2. end事件在当流中没有数据时(数据已传完)触发

可读流状态

readable.readableFlowing 可能有三种状态:null false true

  • 初始时为null
  • 添加data事件后变为true
  • 调用readable.pause()、readable.unpipe()、或接收到背压,会变为false,在这个状态下,为data事件绑定监听器状态不会切换到 true

pipe 与 unpipe

  • 使用pipe时数据流会被自动管理,所以即使可读流更快,目标可写流也不会超负荷。
  • pipe会返回目标流的引用,支持链式操作
  • unpipe用于解绑之前绑定的可写流
const http = require('http')
const fs = require('fs')
const server = http.createServer()
server.on('request'(request, response) => {
    const stream = fs.createReadStream('./file.txt')
    stream.pipe(response)
})
server.listen(8888)

强烈建议使用pipe

pause 与 resume

流动态和静止态的切换,改变data事件是否触发

可写流

概念

可写的流可用于输出数据到底层 I/O:

  • 继承自 stream.Writable
  • 实现一个 _write 方法向底层源数据发送数据

drain

上面pipe提到,使用pipe时,数据流会被自动管理,不会出现写入流超负荷的情况。但如果没用pipe,使用普通的write,写的太快时,调用write() 会返回 false。后续,当可以继续写入时会触发 drain 事件

//....
server.on('request', (request, response) => {
    const stream = fs.createReadStream('./file.txt')
    stream.on("data", (chunk) => {
        response.write(chunk)
    })
    stream.on("end", () => {
        response.end()
    })
    response.on("drain", () => {
        console.log('可以写了')
  })
})
//...

触发drain一定会丢失数据?

当流还未被排空时,调用write()会缓冲chunk,并返回false。 一旦所有当前缓冲的数据块都被排空了(被操作系统接收并传输),则触发'drain'事件。 建议一旦write()返回 false,则不再写入任何数据块,直到'drain'事件被触发。 当流还未被排空时,也是可以调用write(),Node.js 会缓冲所有被写入的数据块,直到达到最大内存占用,这时它会无条件中止。

write 与 end

write是写入数据到可写流,end表明写入完毕,之久不能再调用write了

finish

finish事件在调用end() 且缓冲数据都已传给底层系统之后触发

双工流

双工流允许发送和接收数据:

  • 继承自 stream.Duplex
  • 实现 _read 和 _write 方法

转换流

使用流改变数据为另一种格式,并且高效地管理内存:

  • 继承自 stream.Transform
  • 实现 _transform 方法

示例

使用流 + gzip


const http = require('http');
const fs = require('fs');
const zlib = require('zlib');

http.createServer((req, res) => {
  res.writeHead(200, {
    'content-encoding': 'gzip', // 编码格式告知
  });
  fs.createReadStream(`${__dirname}/index.html`)
    .pipe(zlib.createGzip()) // gzip压缩
    .pipe(res);
}).listen(8000);