node中的Buffer和Stream给刚接触Node的我带来困惑,原因是前端并没有类似概念,而且也确实比较菜
Buffer是缓冲区的意思,Stream是流的意思。在计算机中,缓冲区是存储中间变量,方便CPU读取数据的一块存储区域;流是类比水流形容数据的流动。Buffer和Stream一般都是字节级操作。
在前端,我们只需做字符串级别的操作,很少接触字节、进制等底层操作,一方面这足以满足日常需求,另一方面Javascript这种应用层语言并不是干这个的;然而在后端,处理文件、网络协议、图片、视频等时是非常常见的,尤其像文件、网络流等操作处理的都是二进制数据。为了让javascript能够处理二进制数据,node封装了一个Buffer类,主要用于操作字节,处理二进制数据。
buffer
这是一块内存,临时存储数据。这种不可调整大小的内存旨在处理原始二进制数据,其中的整数限制为 0 到 255 (2^8 - 1) 之间的值,每个整数代表一个字节。
当 *站 视频开始播放时,如果加载比较快,播放流中会出现灰色区域。这个区域是缓冲区,在那里收集和临时存储数据(通常在 RAM 中)以允许连续播放,即使在互联网断开连接的情况下。红色区域为播放区(数据处理区),其长度取决于视频的播放速度和已缓冲的数据量。如果刷新浏览器,则重新初始化临时存储并重新启动进程,stream缓慢向前推动,加载入buffer。
demo
var buf = Buffer.allocUnsafe(15);
var buf1 = Buffer.alloc(15);
console.log(buf);
console.log(buf1);
output
一个 Buffer 类似于一个整数数组,可以取下标,有length属性,有剪切复制操作等,很多API也类似数组,但Buffer的大小在被创建时确定,且无法调整。Buffer处理的是字节,两位十六进制,因此在整数范围就是0~255。
可以看到,Buffer可以与string互相转化,还可以设置字符集编码。Buffer用来处理文件I/O、网络I/O传输的二进制数据,string用来呈现。在处理文件I/O、网络I/O传输的二进制数据时,应该尽量以Buffer形式直接传输,速度会得到很好的提升,但操作字符串比操作Buffer还是快很多的。
Stream
流类比水流形容数据的流动,在文件I/O、网络I/O中数据的传输都可以称之为流,流是能统一描述所有常见输入输出类型的模型,是顺序读写字节序列的抽象表示。数据从A端流向B端与从B端流向A端是不一样的,因此,流是有方向的。A端输入数据到B端,对B就是输入流,得到的对象就是可读流;对A就是输出端、得到的对象是可写流。有的流即可以读又可以写,如TCP连接,Socket连接等,称为读写流(Duplex)。还有一种在读写过程中可以修改和变换数据的读写流称为Transform流。
在node中,这些流中的数据就是Buffer对象,可读、可写流会将数据存储到内部的缓存中,等待被消费;Duplex 和 Transform 则是都维护了两个相互独立的缓存用于读和写。 在维持了合理高效的数据流的同时,也使得对于读和写可以独立进行而互不影响。
在node中,这四种流都是EventEmitter的实例,它们都有close、error事件,可读流具有监听数据到来的data事件等,可写流则具有监听数据已传给低层系统的finish事件等,Duplex 和 Transform 都同时实现了 Readable 和 Writable 的事件和接口 。
- Readable: Allows you to read data from a source.
- Writable: Allows you to write data to a destination.
- Duplex: Allows you to read data from a source and write data to a destination.
- Transform: Allows you to modify or transform data while data is being read or written.
Demo
const fs = require('fs');
const file = fs.createReadStream('readTextFile.txt');
file.on('data', function(data) {
console.log('Data '+ data);
});
file.on('end', function(){
console.log('Hey!, Am Done reading Data');
});
首先,我们创建了一个名为 readTextFile.txt 的文件。然后,使用 fs.createReadStream('filename') 函数创建可读流。流最初处于静态状态,并在您侦听数据事件并附加回调后立即进入流动状态。当没有更多数据可供读取时,流会发出结束事件。
提到流就不得不提到管道的概念,这个概念也非常形象:水流从一端到另一端流动需要管道作为通道或媒介。流也是这样,数据在端之间的传送也需要管道,在node中是这样的:
// 将 readable 中的所有数据通过管道传递给名为 file.txt 的文件
const readable = getReadableStreamSomehow();
const writable = getWritableStreamSomehow('file.txt');
// readable 中的所有数据都传给了 'file.txt'
readable.pipe(writable);
// 对流进行链式地管道操作
const r = fs.createReadStream('file.txt');
const z = zlib.createGzip();
const w = fs.createWriteStream('file.txt.gz');
r.pipe(z).pipe(w);
注意,只有可读流才具有pipe能力,可写流作为目的地。
pipe不仅可以作为通道,还能很好的控制管道里的流,控制读和写的平衡,不让任一方过度操作。另外,pipe可以监听可读流的data、end事件,这样就可以构建快速的响应,防止缓存区爆掉:
// 一个文件下载的例子,使用回调函数的话需要等到服务器读取完文件才能向浏览器发送数据
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(8888) ;
// 而采用流的方式,只要建立连接,就会接受到数据,不用等到服务器缓存完data.txt
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(8888)
filesystems
Node 有一个模块名称 File System (fs),它与计算机上的文件系统一起工作。这个模块 fs 执行了几个可以同步和异步实现的方法,但是在使用它们时没有保证的顺序。 读取、创建、更新、删除和重命名是可以对保存在目录中的任何文本文件执行的一些操作。
fs模块中的所有操作都提供了异步和同步两个版本。fs模块主要由下面几部分组成:
- 对底层POSIX文件系统的封装,对应于操作系统的原生文件操作
- 继承Stream的文件流 fs.createReadStream和fs.createWriteStream
- 同步文件操作方法,如fs.readFileSync、fs.writeFileSync
- 异步文件操作方法, fs.readFile和fs.writeFile
读/写文件都有三种方式,那么区别是什么呢?
- createReadStream/createWriteStream创建一个将文件内容读取为流数据的ReadStream对象,这个方法主要目的就是把数据读入到流中,得到是可读流,方便以流进行操作
- readFile/writeFile:Node.js会将文件内容视为一个整体,为其分配缓存区并且一次性将文件内容读/写取到缓存区中,在这个期间,Node.js将不能执行任何其他处理,所以当读写大文件的时候,有可能造成缓存区“爆仓”
- read/write读/写文件内容是不断地将文件中的一小块内容读/写入缓存区,最后从该缓存区中读取文件内容
区别
- 本质上讲,
fs.readFile()方法是对fs.read()方法的进一步封装,fs.readFile()方法可以方便的读取文件的全部内容。相比fs.readFile()方法,使用fs.read()方法读取文件的全部内容就要复杂的多。首先要用fs.stat或fs.fstat判断文件的大小,然后使用fs.open()创建文件描述符,最后再使用fs.read()方法读取文件内容。
参考: 莫凡大佬的博客