欢迎光临
我们一直在努力

聊聊Node.js中如何实现Stream流(可读、可写、双工和转换流)

本篇文章带大家了解一下Node中的之Stream,介绍一下引入 Stream,实现可读流、可写流、双工流和转换流的方法,希望对大家有所帮助!

引入 Stream

假设我们有这么一个需求,我们需要复制一个文件中的内容到另一个文件中,我们会写出以下代码

const fs = require('fs'); const path = require('path');  const copy = (source, target) => {     fs.readFile(path.resolve(source), (err, data) => {         if(err) {             throw new Error(err.toString());             return;         }         fs.writeFile(path.resolve(target), data, (err) => {             if(!err) {                 console.log("复制成功!");             }         })     }) }

上面的代码很简单,就是先读取 source 文件里面的内容,然后将内容写入到 target 文件中。它的特点是需要读取完 source 里面的所有内容,然后将内容写入到 target 中。

这样做就有一个缺点,当我们读取大文件时,可能会发生内存不够用的情况,因为它会先将文件的所有内容都读取到内存;另外还就是时间,一次性读取一个大文件到内存,是需要比较长的时间的,用户可能会有卡顿的感觉。

另一种解决办法就是边读边写,读取部分文件内容,然后将内容写入到新文件中,这样在内存中的数据只是部分内容,不会占有太多的内存,由于是边读编写,用户可以很快的得到响应,提高用户体验。

在网上找到一幅动图来形象的展示使用流前后数据的流动情况

Node.js 给我们提供 Stream 的 API,它是专门用来处理大文件的。因为数据是一部分一部分的处理,就像是水流一样,所以这个模块的名称就称为 Stream。

const fs = require('fs');  function copy(source, target) {     const rs = fs.createReadStream(source);     const ws = fs.createWriteStream(target);      rs.on('data', data => {         ws.write(data);     });      rs.on('end', () => {         ws.end();     }); }

上面代码的细节将在后文揭晓。

Stream 的分类

Stream 可以分为四类

  • Readable:可读流,数据的提供者
  • Writeable:可写流,数据的消费者
  • Duplex:可写可读流(双工流)
  • Transform:是 Duplex 的特殊情况,转换流,对输入的数据进行处理,然后输出

可读流与可写流是基础,常见的可读流与可写流如下

可读流 可写流
HTTP Request HTTP Reponse
fs read streams fs write streams
process.stdin process.stdout
TCP sockets TCP sockets
zlib streams zlib streams
crypto streams crypto streams

Stream 是 EventEmitter 的实例,有自定义的事件。

Readable Stream

可读流有两个模式,暂停模式与流动模式。当我们创建一个流时,如果我们监听了 readable 事件,它就会来到暂停模式,在暂停模式下,它会不断的读取数据到缓冲区,当读取到的数据超过预设的大小时,它由属性 highWaterMark 指定(默认为 64kB),便会触发 readable 事件,readable 事件的触发有两种情况:

  • 缓存区中的数据达到 highWaterMark 预设的大小
  • 数据源的数据已经被读取完毕
const fs = require('fs');  const rs = fs.createReadStream('a.txt', {     highWaterMark: 1 // 缓存区最多存储 1 字节 });  rs.on('readable', () => {     let data;     while(data=rs.read()) {         console.log(data.toString());     } })

上面的程序设置 highWaterMark 为 1,即每次读取到一个字节便会触发 readable 命令,每次当触发 readable 命令时,我们调用可读流的 read([size]) 方法从缓冲区中读取数据(读取到的数据为 Buffer),然后打印到控制台。

当我们为可读流绑定 data 事件时,可读流便会切换到流动状态,当位于流动状态时,可读流会自动的从文件中读取内容到缓冲区,当缓冲区中的内容大于设定便宜香港vps的 highWaterMark 的大小时,便会触发 data 事件,将缓冲区中的数据传递给 data 事件绑定的函数。以上过程会自动不断进行。当文件中的所有内容都被读取完成时,那么就会触发 end 事件。

const fs = require('fs');  const rs = fs.createReadStream('a.txt', {     highWaterMark: 2 });  rs.on('data', data => {     console.log(data.toString()); });  rs.on('end', () => {     console.log("文件读取完毕!"); });

暂停模式像是手动步枪,而流动模式则像是自动步枪。暂停模式与流动模式也可以相互切换,通过 pause() 可以从流动状态切换到暂停状态,通过 resume() 则可以从暂停模式切换到流动模式。

可读流的一个经典实例就是 http 中的请求对象 req,下面的程序展示了通过监听 reqdata 事件来读取 HTTP 请求体中的内容

const http = require('http');  const app = http.createServer();  app.on('request', (req, res) => {     let datas = [];     req.on('data', data => {         datas.push(data);     });      req.on('end', () => {         req.body = Buffer.concat(datas);         // 当读取完 body 中的内容之后,将内容返回给客户端         res.end(req.body);     }); })  app.listen(3000, () => {     console.log("服务启动在 3000 端口... ..."); })

Writable Stream

可写流与可读流相似,当我们向可写流写入数据时(通过可写流的 write() 方法写数据),会直接将数据写入到文件中,如果写入的数据比较慢的话,那就就会将数据写入到缓冲区,当缓冲区中的内容达到 highWaterMark 设定的大小时,write 方法就会返回一个 false,表明不能接受

本文来源网站:info110.com,若侵权,请联系删除。

赞(0)
【声明】:本博客不参与任何交易,也非中介,仅记录个人感兴趣的主机测评结果和优惠活动,内容均不作直接、间接、法定、约定的保证。访问本博客请务必遵守有关互联网的相关法律、规定与规则。一旦您访问本博客,即表示您已经知晓并接受了此声明通告。