🌟前言
哈喽小伙伴们,新的专栏 Node 已开启;这个专栏里边会收录一些Node的基础知识和项目实战;今天我们开始这个专栏的第三篇文章,带领大家初识一下Buffer 与 Stream;让我们一起来看看吧🤘
🌟Buffer
JavaScript最初是被设计为处理html文档的,因此并不善于处理二进制数据。Node中需要处理网络协议、操作数据库、处理图片、文件上传等,还需要处理大量二进制数据,为此Node提供了Buffer类,该类封装了对访问连续内存块的操作,可以处理内存中的数据及创建、切分缓冲区,还可以在两个缓冲区之间复制内存数据。
🌟 Buffer结构
Buffer是一个典型的Javascript和C++结合的模块,性能相关部分用C++实现,非性能相关部分用javascript实现。
node在进程启动时Buffer就已经加装进入内存,并将其放入全局对象,因此无需require
Buffer对象:类似于数组,其元素是16进制的两位数。
🌟 什么时候用Buffer
纯粹的Javascript支持unicode码而对二进制不是很支持,当解决TCP流或者文件流的时候,处理流是有必要的,我们保存非utf-8字符串,2进制等等其他格式的时候,我们就必须得使用”Buffer“。
🌟 Buffer的转换
Buffer对象可以和字符串相互转换,支持的编码类型如下: ASCII、UTF-8、Base64、Binary(二进制)、Hex(十六进制)
🌟 Buffer使用
方法 | 类型 | 描述 |
Buffer.from(array) | 创建 | array 创建一个新的 Buffer |
Buffer.from(string[,encoding]) | 创建 | 新建一个包含所给的 JavaScript 字符串 string 的 Buffer 。 encoding 参数指定 string 的字符编码。 |
Buffer.alloc(size[,val[,encoding]]) | 创建 | 分配一个大小为 size 字节的新建的 Buffer 。 如果 fill 为 undefined ,则该 Buffer 会用 0 填充。 |
Buffer.alloc(size[,val[,encoding]]) | 创建 | 分配一个大小为 size 字节的新建的 Buffer 。 如果 fill 为 undefined ,则该 Buffer 会用 0 填充。 |
Buffer.concat(list[,totallength]) | 合并 | 返回一个合并了 list 中所有 Buffer 实例的新建的 Buffer |
buf.toString([encoding], [start], [end]) | 返回 | 根据 encoding 指定的字符编码解码 buf 成一个字符串。 |
buf.toJSON() | 返回 | 返回 buf 的 JSON 格式。 |
Buffer.isEncoding(encoding) | 检测 | 如果 encoding 是一个支持的字符编码则返回 true,否则返回 false 。 |
Buffer.isBuffer(obj) | 检测 | 如果 obj 是一个 Buffer 则返回 true ,否则返回 false |
Buffer.byteLength(buf) | 获取 | 获取字节长度 |
buf.equals(otherBuffer) | 检测 | 如果 buf 与 otherBuffer 具有完全相同的字节,则返回 true,否则返回 false。 |
🌟 创建Buffer
Buffer.from(array) Buffer.from(string[,encoding]) Buffer.alloc(size[,val[,encoding]])
🌟 字符串转Buffer
# 默认UTF-8 Buffer.from(string[,encoding])
🌟 Buffer转字符串
buf.toString([encoding], [start], [end])
🌟 拼接Buffer
Buffer.concat(list[,totallength])
🌟 Buffer不支持的编码类型
Buffer.isEncoding(encoding)
🌟 判断是不是Buffer
Buffer.isBuffer(obj)
🌟 获取字节长度
Buffer.byteLength(buf)
🌟 判断两个Buffer字节是否相同
如果 buf 与 otherBuffer 具有完全相同的字节,则返回 true,否则返回 false。
buf.equals(otherBuffer)
🌟Nodejs Stream(流)
在之前我们学习过fs模块,fs模块中有fs.readFile 与 fs.writeFile 这两个方法读取和写入操作,但是这两个方法时将文件作为一个整体做读取、写入操作。
- fs.readFile 将文件做为整体读入缓存区
- fs.writeFile 将数据做为整体写入文件
假设我们要读取的文件为100G,内存只有8G,那么内存根本无法容纳这100G的数据,那么我们该如何去操作这些大数据呢?
假设客户端向服务器端传递100G的数据,数据是分为一小段一小段进行传输的。到了服务器端再将其组合起来。
传输数据的时候最小单位就是字节。
🌟 Node.js Stream
Stream是Node.js中处理数据的抽象接口,Node中有很多对象实现了这个接口。
Stream是Node.js中非常重要的一个模块,应用广泛。一个流是一个具备了可读、可写或既可读又可写能力的接口,通过这些接口,我们可以和磁盘文件、套接字、HTTP请求来交互,实现数据从一个地方流动到另一个地方的功能。
例如
:对http服务器发起请求的request对象就是一个Stream,还有stdout(标准输出)
🌟为什么应该使用流
在node中,I/O都是异步的,所以在和硬盘以及网络的交互过程中会涉及到传递回调函数的过程。你之前可能会写出这样的代码:
var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { fs.readFile(__dirname + '/data.txt', function (err, data) { res.end(data); }); }); server.listen(8000);
上面的这段代码并没有什么问题,但是在每次请求时,我们都会把整个data.txt文件读入到内存中,然后再把结果返回给客户端。想想看,如果data.txt文件非常大,在响应大量用户的并发请求时,程序可能会消耗大量的内存,这样很可能会造成用户连接缓慢的问题。
其次,上面的代码可能会造成很不好的用户体验,因为用户在接收到任何的内容之前首先需要等待程序将文件内容完全读入到内存中。
所幸的是,(req,res)参数都是流对象,这意味着我们可以使用一种更好的方法来实现上面的需求:
var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { var stream = fs.createReadStream(__dirname + '/data.txt'); stream.pipe(res); }); server.listen(8000);
在这里,.pipe()方法会自动帮助我们监听data和end事件。上面的这段代码不仅简洁,而且data.txt文件中每一小段数据都将源源不断的发送到客户端。
除此之外,使用.pipe()方法还有别的好处,比如说它可以自动控制后端压力,以便在客户端连接缓慢的时候node可以将尽可能少的缓存放到内存中。
🌟Stream的作用
传统程序在执行过程中,会边读边写,读写的速度不一样会导致数据丢失;且内存受限,读取存取速度有限。采用流以后程序会读一部分写一部分,保障数据不缺失。
Stream的作用如下:
- 保证程序运行效率
- 防止数据丢失
- 防止内存的溢出
🌟Node.js Stream
Stream是Node.js中处理数据的抽象接口,Node中有很多对象实现了这个接口。 例如:对http服务器发起请求的request对象就是一个Stream,还有stdout(标准输出)
该stream模块可以使用访问:
const stream = require('stream');
🌟四种基本的流类型
- Readable - 读取流 (例如 fs.createReadStream())
- Writable - 写入流 (例如写 fs.createWriteStream())
- Duplex - 读写流(即双工流) (例如 net.Socket)
- Transform - 读写流(操作被写入数据,然后读出结果) (例如 zlib.createDeflate())
🌟1.readStream 读取流
fs.createReadStream(path,[opts]); //创建可读流
🌟参数
- path 创建读取流指定的文件路径
- opts
- flags 对文件采取何种操作,默认为 ‘r’
- encoding 指定 编码 ,默认为null
- start 用整数表示文件 开始 读取的字节数的索引位置
- end 用整数表示文件 结束 读取的字节数的索引位置(包括end位置)
- highWaterMark 最高水位线,停止从底层资源读取前内部缓冲区最多能存放的字节数。缺省为 64kb
{"encoding":"utf-8","start":0,"end":2,"highWaterMark":4}
🌟事件
- data 当数据读取的时候
- end 没有更多的数据可读时触发
- error 当发生错误时候触发
🌟方法
- setEncoding 指定 编码
- pause() 读取数据暂停(什么时候暂停?读入流大于写入流调用)
- resume() 通知对象 恢复 触发data事件继续读取数据
- pipe() 管道 由读取流安全的传输到下一个流
🌟2.writeStream 写入流
fs.createWriteStream(path,[opts]); //创建一个可写入流
path 读取的文件路径
options
flags 对文件采取何种 操作 ,默认为 ‘w’
encoding 指定 编码 ,默认为null
autoClose 是否 关闭 文件描述符
start 用整数表示文件 开始 字节数的写入位置
highWaterMark 最高水位线,write()开始返回 false 的 缓冲大小 。缺省为 16kb
🌟 事件
drain 当前内存数据完全都写入流的时候触发
finish 当数据全部都写完后触发
🌟方法
🌟 write()
write(chunk,[encoding],[callback])
要往写入流写入数据的时候触发
参数
- chunk 要 写入 的数据,Buffer或字符串对象,必须指定
- encoding 写入 编码 ,chunk为字符串时有用,可选
- callback 写入成功后的 回调
返回值为布尔值,系统缓存区定满时为false,未满时为true
🌟end()
writable.end(chunk,[encoding],[callback]);
在写入文件时,当不再需要写入数据时可调用该方法关闭文件。 迫使系 统缓存区的数据立即写入文件中。
🌟3.管道流
管道流,pipe()方法的初衷,是将数据的滞留量限制到一个可接受的水平,以使得不同速度的来源和目标不会淹没可用内存。
readStream.pipe(writeStream,[options]);
🌟options
- end 为true时表示数据读取完毕后立刻将缓存区中的数据写入目 标文件并 关闭 文件 无论哪一种流,都会使用.pipe()方法来实现输出和输出。
fs.createReadStream().pipe(fs.createWriteStream());
- pipe()函数很简单,它仅仅是接受一个源头src并将数据输出到一个可写的流dst中:
src.pipe(dst)
- pipe(dst)将会返回dst因此你可以链式调用多个流:
a.pipe(b).pipe(c).pipe(d)
上面的代码也可以等价为:
a.pipe(b); b.pipe(c); c.pipe(d);
🌟写在最后
更多Node知识以及API请大家持续关注,尽请期待。各位小伙伴让我们 let’s be prepared at all times!