前言

昨天我们讲述了 Buffer类 的基础用法,今天我们介绍一下 Buffer类 的一些应用以及 流(Stream) 的概念和用法。

Buffer 使用

Buffer 拼接

Buffer 在使用时,通常是以一段一段的方式传输。以下是一段经典的从输入流中读取内容的代码:

const fs = require("fs");
// const readFs = fs.createReadStream("./readExam.md", {
//   highWaterMark: 1
// });
const readFs = fs.createReadStream("./readExam.md");
let data = "";
readFs.on("data", (chunk) => {
    data  = chunk;
});
readFs.on("end", () => {
    console.log("buffer value: ", data);
});

💡 data事件中获取的 chunk对象Buffer对象String对象,然后与 data变量 拼接成目标 Buffer对象。

上述的代码中我们构造了一个可读流。值得一提的是,可读流有一个设置编码的方法:

readable.setEncoding(encoding);

该方法能指定 data事件 中传递的元素的编码类型,避免发生一些特殊的错误:

const readFs = fs.createReadStream("./readExam.md");
readFs.setEncoding('utf-8');

编码问题

在不设置 highWaterMark 属性的情况下,你无需显示地去调用 setEncoding 方法,data事件默认就能接受字符串或者 Buffer 对象两种参数。但你仍需注意,目前仅支持 UTF8UTF16LE 两种编码的字符串,所以如果读取的目标文件是其他编码的,打印结果将会是乱码!

💡 假设每读取一个Buffer就会触发一次data事件,那么无论如何设置编码,触发data事件的次数依旧相同。也就是说,如果你读的文件中内容是汉字,要触发三次data事件才会进行一次拼接。因此在这种情况下中文会出现乱码。

而在调用setEncoding()时,可读流对象在内部设置了一个decoder对象。每次data事件都通过该decoder对象进行Buffer到字符串的解码,然后传递给调用者。而decoder内部是会对是否为宽字节进行判断,从而进行转码。

拼接的正确姿势

正确的拼接方式是用一个数组来存储接收到的所有Buffer片段并记录下所有片段的总长度,然后调用Buffer.concat() 方法生成一个合并的Buffer对象。

const fs = require("fs");
const readFs = fs.createReadStream("./readExam.md");
let chunks = [];
let size = 0;
readFs.on("data", (chunk) => {
  const chunkBuf = new Buffer.from(chunk);
  chunks.push(chunkBuf);
  size  = chunkBuf.length;
});
readFs.on("end", () => {
  const buf = Buffer.concat(chunks, size);
  const str = buf.toString(); // 对应编码方式,如果不支持则需要引入外部库
})

文件读取

💡 Nodejs 提供了一个通过 Buffer 读取文件的方法 fs.readFile(),可以简化读取文件的操作。同时该方法还有 Sync 模式,及它的同步方法,返回一个Buffer对象。

但是注意,由于V8的内存限制,你无法通过 fs.readFile()fs.writeFile() 直接对大文件进行字符串操作,而需改用 fs.createReadStream()fs.createWriteStream() 方法通过流的方式实现对大文件的操作。具体请参考接下来的 Stream 的介绍。

而如果不需要进行字符串层面的操作,则不需要借助V8来处理,只进行纯粹的Buffer操作,这不会受到V8堆内存的限制,只会受到电脑物理内存的限制。

性能

Buffer 的使用除了与字符串的转换有性能损耗外,在文件的读取时,有一个highWaterMark设置对性能的影响至关重要。其默认值为64KB。

fs.createReadStream()的工作方式是在内存中准备一段Buffer内存,然后在fs.read()读取时逐步从磁盘中将字节复制到Buffer内存中。完成一次读取时,则从这个Buffer中通过slice()方法取出部分数据作为一个小Buffer对象,再通过data事件传递给调用方。如果Buffer用完,则重新分配一个;如果还有剩余,则继续使用。而每次读取的长度就是户指定的 highWaterMark ,在合理范围内,该值越大,读取速度越快。

fs.createReadStream(path, [options])

💡 最开始我们将 highWaterMark 设置为 1 ,然后读取中文出现乱码也是这个原因

在网络中的应用

在Web应用中,字符串转换到Buffer是时时刻刻发生的,提高字符串到Buffer的转换效率,可以很大程度地提高网络吞吐率。因此,Nodejs内部会通过预先转换静态内容为Buffer对象缓存着,以减少CPU的重复使用,节省服务器资源。

const http = require('http');
const HOST = "127.0.0.1";
const PORT = 6869;
const server = http.createServer();
server.listen({
  port: PORT,
  host: HOST
}, () => {
  console.log(`server listen on `, server.address());
});
let resData = "";
for (let i = 0; i < 1024*10; i  ) {
  resData  = "a";
}
// resData = new Buffer.from(resData);
// 监听客户端发起的 request 
server.on('request', (req, res) => {
  console.log('connect success!\n');
  res.writeHead(200);
  res.end(resData);
})
server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

💡 你无需显示地调用18行代码。

流 Stream

Nodejs 中原生内置的 stream模块 用于处理流式数据,许多核心模块都在其内部实现了流操作。流还适用于网络传输、JSON解析器、RFC(远程调用)等。Stream 继承自 EventEmitter,具备基本的自定义事件功能,同时抽象出标准的事件和方法它拥有四个抽象类:

  • Readable:可读流,读取底层的I/O数据源。
  • Writeable:可写流,将数据写入到目标中。
  • Duplex:双工流,即可读也可写。
  • Transform:转换流,会修改数据的双工流。

管道 pipe()

在可读流中,有一个管道方法:pipe(),它的作用是关联可读流与可写流,让数据通过管道从可读流进入到可写流中。pipe()方法能接收一个Writable对象,并返回对目标流的引用,从而可形成链式调用。

你可以用这个方法改写之前的案例:

const fs = require('fs');
const readable = fs.createReadStream('./origin.txt');
const writable = fs.createWriteStream('./target.txt');
readable.pipe(writable);
const fs = require("fs");
const readFs = fs.createReadStream("./readExam.md");
const writeFs = fs.createWriteStream("./outExam.md");
// 1.writ end
readFs.on("data", (chunk) => {
    // writeFs.write(chunk);
});
readFs.on("end", () => {
  // writeFs.end();
})
// 2.pipe
readFs.pipe(writeFs);

💡 之前我们提到的内存限制,是因为V8本身是有内存限制的,而通过

EventEmitter

Nodejs 的事件模块目前只包含一个 EventEmitter类(即事件触发器),所有能触发事件的对象都是 EventEmitter类 的实例。EventEmitter 通常被用作基类,在 Nodejs 内部,凡是提供事件机制的模块都会继承它。

声明了一个EventEmitter实例,on()方法用于注册监听器,emit()方法用于触发事件。在调用emit()方法时,传递了自定义的type参数。

const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('click', (type) => {
  console.log(`触发${type}事件`);
});
myEmitter.emit('click', "点击");

💡 可注册多个相同名称的事件,监听器会按照添加顺序依次调用。事件模块还提供了很多其它方法,例如 off() 用于解除事件绑定,once() 可以只监听一次事件。

总结

本节介绍了 Nodejs 中 Buffer对象 的一些具体使用方法和说明,并借此提及 Stream 的相关内容,之后我将介绍一下 Nodejs 提供的标准 I/O 方法,更多关于Nodejs Buffer Stream流的资料请关注Devmax其它相关文章!

Nodejs Buffer的使用及Stream流和事件机制详解的更多相关文章

  1. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  2. 浅析Nodejs npm常用命令

    这篇文章主要介绍了浅析Nodejs npm常用命令的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下

  3. nodejs 使用nodejs-websocket模块实现点对点实时通讯

    这篇文章主要介绍了nodejs 使用nodejs-websocket模块实现点对点实时通讯的实例代码,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  4. nodeJs链接Mysql做增删改查的简单操作

    本篇文章主要介绍了nodeJs链接Mysql做增删改查的简单操作,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. Nodejs中使用captchapng模块生成图片验证码

    本篇文章主要介绍了Nodejs中使用captchapng模块实现图片验证码,非常具有实用价值,需要的朋友可以参考下

  6. nodejs 图片预览和上传的示例代码

    本篇文章主要介绍了nodejs 图片预览和上传的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. nodejs中函数的调用实例详解

    本文通过实例代码给大家介绍了nodejs函数的调用,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  8. NodeJS使用formidable实现文件上传

    这篇文章主要为大家详细介绍了NodeJS使用formidable实现文件上传的相关方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Nodejs获取网络数据并生成Excel表格

    这篇文章主要为大家详细介绍了Nodejs获取网络数据并生成Excel表格的具体实现方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. NodeJS实现不可逆加密与密码密文保存的方法

    这篇文章主要介绍了NodeJS实现不可逆加密与密码密文保存的方法,简单讲述了不可逆加密与密码密文保存的原理并结合实例形式分析了nodejs相关加密操作实现技巧,需要的朋友可以参考下

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部