Buffer的理解

  • Sorzen
  • 7 Minutes
  • April 12, 2020

什么是Buffer

Buffer是一个像Array的对象,但它主要用于操作字节。在引入 TypedArray 之前,JavaScript 语言没有用于读取或操作二进制数据流的机制。 Buffer 类是作为 Node.js API 的一部分引入的,用于在 TCP 流、文件系统操作、以及其他上下文中与八位字节流进行交互。

Buffer的模块结构

Buffer所占用的内存不是通过V8分配的,属于堆内存。由于V8垃圾回收性能的影响,将常用的操作对象用更高效和专有的内存分配回收策略进行管理。
Buffer在Node进程启动时就已经加载,并将其放在全局对象上。所以使用Buffer时,无需通过require()即可直接使用。

Buffer对象

Buffer对象类似于数组,可以访问length属性得到长度。

1
2
var buf = new Buffer(100)
console.log(buf.length) // 100

Buffer内存分配

Buffer对象的内存分配不是在V8d的堆内存中,而是在Node的C++层面实现内存的申请的。因为处理大量的字节数据不能采用需要一点内存就向操作系统申请一点内存的方式,这可能造成大量的内存申请的系统调用,对操作系统有一定压力。为此Node在内存的使用上应用的是在C++层面申请内存、在JavaScript中分配内存的策略。
为了高效的使用申请的内存,Node采用slab分配机制。slab是一种动态内存管理机制。
简单而言,slab就是一块申请好的固定大小的内存区域。slab具有如下三种状态:

当需要一个Buffer对象时:

1
new Buffer(size)

Node以8KB为界限来区分Buffer是大对象还是小对象:

1
Buffer.pollSize = 8 * 1024

分配小Buffer对象

如果指定Buffer的大小少于8KB,Node会按照小对象方式进行分配。Buffer的分配过程中主要是用一个局部变量pool作为中间处理对象,处于分配状态的slab单元都指向它。
同一个slab可能分配给多个Buffer对象使用,只有这些小Buffer对象在作用域释放并都可以回收时,slab的8KB空间才会被回收。

分配大Buffer对象

如果需要分配超过8KB的Buffer对象,将会直接分配一个SlowBuffer对象作为slab单元,这个slab单元将会被这个大Buffer对象独占。

Buffer的转换

字符串转Buffer

字符串转Buffer对象主要是通过构造函数完成的:

1
new Buffer(str, [encoding])

通过构造函数转换的Buffer对象,存储的只能是一种编码类型。encoding参数不传递时,默认按UTF-8编码进行转码和存储。
一个Buffer对象可以存储不同编码类型的字符串转码的值,调用write()方法可以实现:

1
buf.write(string, [offset], [lenght], [encoding])

由于可以不断写入内容到Buffer对象,并且每次写入可以指定编码,所以Buffer对象中可以存在多种编码转换后的内容。

Buffer转字符串

实现Buffer向字符串的转化十分简单,Buffer对象的toString()可以将Buffer对象转换为字符串:

1
buf.toString([encoding], [start], [end])

通过设置encoding、start、end这3个参数实现整体或局部的转换。如果Buffer对象由多种编码写入,就需要在局部制定不同的编码,才能转回正常的编码。

Buffer的拼接

Buffer在使用场景中,通常是以一段一段的方式传输。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var fs = require('fs')
var rs = fs.createReadStream('test.txt')

var chunks = []; // 缓存数据
var size = 0; // 记录总大小

// 进行数据读取
rs.on('data', (trunk) => { // trunk对象即是Buffer对象
chunks.push(trunk)
size += chunks.length
})

// 读取完毕
res.on('end', () => {
var buf = Buffer.concat(chunks, size)
var str = iconv.decode(buf, 'utf8')
console.log(str)
})

Buffer与性能

Buffer在文件I/O和网络I/O中运用广泛,尤其在网络传输中。在应用中我们通常会操作字符串,但一旦在网络传输中,都需要转换为Buffer,以二进制数据传输。在web应用中,字符串转换到Buffer是时时刻刻发生的,提高字符串到Buffer的转换效率。可以很大程度上提高网络的吞吐率。
通过预先转换静态内容为Buffer对象,可以有效地减少cpu的重复使用,节省服务器资源。在Node构建的web应用中,可以选择将页面中的动态内容和静态内容分离,静态内容部分可以通过预先转换为Buffer的方式,是性能得到提升。由于文件自身是二进制数据,所以在不需要改变内容的场景下,尽量只读取buffer,然后直接传输,不做额外的转换,避免损耗。