node中的Buffer与Stream

737 阅读6分钟

说到Buffer,官方是这么说的

…JavaScript语言没有读取或操作二进制数据流的机制。 Buffer 类被引入作为 Node.js API 的一部分,使其可以在 TCP 流或文件系统操作等场景中处理二进制数据流。

可以这么理解:Buffer类被引入到Node.js的API中的目的,让其与二进制数据流的操作和交互成为可能。

二进制数据是什么?

你应该已经知道,计算机存储和表示数据使用二进制的。比如,下面这些是5个二进制数,5个不同的1和0序列:

10, 01, 001, 1110, 00101011

二进制中的每个数字,0或1叫做位(bit),也就是Binary digIT的缩写。

为了能够存储和表示这些数据,计算机需要将数据转换为二进制形式。比如,要存储数字12,计算机需要将12转化为二进制1100。计算机怎么知道要如何去转换?这就完全是一个数学问题了。计算机是知道怎么去处理的,有兴趣的可以自己查阅。

但是,我们日常工作的数据类型不仅仅是数字。我们还有字符、图片甚至视频。计算机是知道如何将这些表示为二进制的?

字符集就是定义数字所代表的字符的一个规则表,同样定义了怎样用二进制存储和表示。

用多少位来表示一个数字,这个就叫字符编码(Character Encoding)。

总之,计算机会将无论图片、视频或其他数据都转换为二进制并存储,这就是我们说的二进制数据。现在我们了解了什么是二进制数据,但是我们介绍buffer的时候说的二进制数据流(streams of binary data)又是什么呢?

Stream

在Node.js中,流(stream)就是一系列从A点到B点移动的数据。完整点说,就是当你有一个很大的数据需要传输、搬运时,你不需要等待所有数据都传输完成才开始下一步工作。

实际上,巨型数据会被分割成小块(chunks)进行传输。所以,buffer的原始定义中所说的(“streams of binary data… in the context of… file system”)意思就是说二进制数据在文件系统中的传输。比如,将file1.txt的文字存储到file2.txt中。

Stream 是 Node.js 最基本的概念之一,Node.js 内部的大部分与 IO 相关的模块,比如 http、net、fs,都是建立在各种 Stream 之上的。

下面这个经典的例子应该大部分人都知道,对于大文件,我们不需要把它完全读入内存,而是使用 Stream 流式地把它发送出去:

在业务代码中合理地使用 Stream 能很大程度地提升性能,当然在实际的业务中我们很可能会忽略这一点

创建可读流
const rs = fs.createReadStream('./data.txt') 
// 设置编码为 utf8。
readerStream.setEncoding('UTF8');


//写入流
var data = '菜鸟教程官网地址:www.runoob.com';
// 创建一个可以写入的流,写入到文件 data.txt 中
var ws= fs.createWriteStream('output.txt');
// 使用 utf8 编码写入数据
ws.write(data,'UTF8');

但是,buffer到底在流(stream)中,是如何操作二进制数据的?buffer到底是个什么呢?

管道流

管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。

如上面的图片所示,我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。 以下实例我们通过读取一个文件内容并将内容写入到另外一个文件中。 创建 main.js 文件, 代码如下:

// 创建一个可读流
var readerStream = fs.createReadStream('./data/data.txt');
// 创建一个可写流
var writerStream = fs.createWriteStream('./data/data2.txt');
// 管道读写操作
// 读取 input.txt 文件内容,并将内容写入到 data2.txt 文件中
readerStream.pipe(writerStream);

console.log("程序执行完毕");

比较 readFile 和 createReadStream readFile的方式(bad)

const http = require('http');
const fs = require('fs');
 
http.createServer(function (req, res) {
    fs.readFile(__dirname + '/data.txt', function (err, data) {
        console.log(data);
        res.end(data.toString());
    });
}).listen(9000);

createReadStream的方式(good)

http.createServer(function (req, res) {
    const rs = fs.createReadStream(__dirname + '/data/data.txt');
      var data = { name: 'laney' };

      //在内部不断触发rs.emit('data',数据);data不能更改,留动模式开启后,数据会疯狂触发data事件
      let arr=[];
      rs.on('data',function (chunk) {  //chunk是buffer类型
          arr.push(chunk);
      })

      //监听文件读取完毕,会自动触发一次end事件,没有读取完是不会触发的
      //Buffer.concat合并小buffer
      rs.on('end',function () {
         console.log('Buffer',Buffer.concat(arr).toString());
         console.log('arrBuffer',arr)
        // res.end(Buffer.concat(arr))

        res.end(Buffer.concat(arr).toString())

      })

       // 监听错误
       rs.on('error',function (err) {
          console.log(err);
      })
      // rs.resume() 恢复读取
      // rs.pause()  暂停读取
      //这两个都控制是否继续触发data事件
    
}).listen(9000);

Buffer

buffer翻译的意思是缓冲区

在node中,Buffer是用于存储二进制数据的,在内存中新开辟一块天地,在堆以外的内存

一个关于buffer很典型的例子,就是你在线看视频的时候。如果你的网络足够快,数据流(stream)就可以足够快,可以让buffer迅速填满然后发送和处理,然后处理另一个,再发送,再另一个,再发送,然后整个stream完成。。。。。。

Buffer应用场景

当数据流很大的时候,进行buffer缓存一下,进行进一步获取

buffer不在node进程内存里面,所以可以用于存储大的文件,但是还是有限制 的32位系统的大约是1G,64位的系统 大约是2G

如何使用

在stream中,Node.js会自动帮你创建buffer之外,你也可以创建自己的buffer并操作它:

手动创建buffer

1. Buffer存储数据未确定

// 创建一个大小为10的空buffer
// 这个buffer只能承载10个字节的内容
const buf1 = Buffer.alloc(10);

// 根据内容直接创建buffer
const buf2 = Buffer.from("hello buffer");
// 创建一个大小为2的空buffer
// 这个buffer只能承载2个字节的内容
const buf1 = Buffer.alloc(2,'abcd');

//解码buffer  toString
console.log(buf1.toString())  // ab 

// 检查buffer的大小  length
console.log(buf1.length)

// toJSON 方法可以将数据进行Unicode编码并展示
console.log(buf1.toJSON())

2.Buffer存储数据确定

Buffer.from(obj) // obj支持的类型string, buffer, arrayBuffer, array, or array-like object

若要传入数字可以采用传入数组的方式:

const buf = Buffer.from([1, 2, 3, 4]);
console.log(buf); //  <Buffer 01 02 03 04>

// 根据内容直接创建buffer,不指定缓存大小,根据数据自动盛满并创建
const buf2 = Buffer.from("hello buffer");

//写入数据到buffer
buf2.write("Buffer really rocks!")
console.log(buf2.toString())  //     //从缓冲区读取数据

操作buffer:

// 检查下buffer的结构

buf1.toJSON()
// { type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }
// 一个空的buffer

buf2.toJSON()
// { type: 'Buffer',data: [ 104, 101, 108, 108, 111, 32, 98, 117, 102, 102, 101, 114 ] }
// the toJSON() 方法可以将数据进行Unicode编码并展示
   
// 检查buffer的大小

buf1.length // 10
buf2.length //12 

//写入数据到buffer
buf1.write("Buffer really rocks!")

//解码buffer

buf1.toString() // 'Buffer rea'

需要谨记一点:new Buffer(xxxx) 方式已经不推荐使用了

参考:nodejs.org/dist/latest…