理解Buffer

292 阅读4分钟

描述

JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。但在处理像TCP流或文件流时,必须使用到二进制数据。
因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区
—— 菜鸟教程

创建buffer实例

在v6.0之前创建Buffer对象直接使用new Buffer()构造函数来创建对象实例,但是Buffer对内存的权限操作相比很大,可以直接捕获一些敏感信息,所以在v6.0以后,官方文档里面建议使用 Buffer.from() 接口去创建Buffer对象。

Buffer.from

  • Buffer.from(array)
    返回一个被 array 的值初始化的新的 Buffer 实例(传入的 array 的元素只能是数字,不然就会自动被 0 覆盖)
  • Buffer.from(arrayBuffer[, byteOffset[, length]])
    返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。
  • Buffer.from(buffer)
    复制传入的 Buffer 实例的数据,并返回一个新的 Buffer 实例
  • Buffer.from(string[, encoding])
    返回一个被 string 的值初始化的新的 Buffer 实例,encoding默认utf8

Xnip2022-07-04_09-23-47.jpg 其中31 32,是字符'1','2'的utf8编码16进制值 Xnip2022-07-04_09-24-34.jpg

Buffer.alloc

Buffer.alloc(size[, fill[, encoding]])
返回一个指定大小的 Buffer 实例,如果没有设置 fill,则默认填满 0 Xnip2022-07-04_09-33-57.jpg

Buffer.allocUnsafe

Buffer.allocUnsafe(size) 
返回一个指定大小的 Buffer 实例,但是它不会被初始化,所以它可能包含敏感的数据

Xnip2022-07-06_08-33-00.jpg 多次运行,会得到不同的结果。

其他API

类方法

  1. Buffer.isEncoding(encoding)
    Buffer是否支持此编码
console.log(Buffer.isEncoding('utf8'));
console.log(Buffer.isEncoding('utf16'));

Xnip2022-07-11_09-16-05.jpg

  1. Buffer.isBuffer(Object)
    判断对象是否是buffer实例
console.log(Buffer.isBuffer('test'));
console.log(Buffer.isBuffer(Buffer.alloc(10)));

Xnip2022-07-11_09-18-55.jpg

  1. Buffer.byteLength(string, [encoding])
    字符串所占字节长度,默认utf8
console.log(Buffer.byteLength('hello', 'utf8'));
const buffer = Buffer.from('hello'); //aGVsbG8=
console.log(Buffer.byteLength(buffer.toString('base64'), 'base64'));

Xnip2022-07-11_09-23-42.jpg

  1. Buffer.concat(Buffer[], [length])
    将多个buffer对象拼接成一个新的buffer对象。length是指合并后的buffer对象长度,如果不指定,nodejs内部会先计算
const buffer1 = Buffer.from('hello'); 
const buffer2 = Buffer.from(' nodejs');

const buffer = Buffer.concat([buffer1, buffer2]);
console.log(`${buffer.toString()}, ${buffer1.toString()}, ${buffer2.toString()}`);

Xnip2022-07-11_09-31-13.jpg

实例属性

  1. length
    返回buffer对象所占内存长度,单位byte
const buffer = Buffer.from('富途');
console.log(buffer.length);

Xnip2022-07-11_09-12-40.jpg

实例方法

  1. toString([encoding], [start], [end])
    buffer转字符串,这里之前用过了,不再举例。可支持的encoding见后文
  2. toJSON
const buffer = Buffer.from('hello');
console.log(buffer);
console.log(buffer.toJSON());
// JSON.stringify的对象是buffer时,会先调用toJSON方法
console.log(JSON.stringify(buffer));

Xnip2022-07-11_09-09-51.jpg

  1. wite(string, [start], [encoding])
const buffer = Buffer.alloc(10);
buffer.write(' css', 1);
console.log('length is ' + buffer.length +', string is ' + buffer.toString());

Xnip2022-07-11_08-58-42.jpg

  1. slice(start, end)
const buffer = Buffer.from('你好');
console.log(buffer.length);

const buffer2 = buffer.slice(0, 3);
console.log(buffer2.toString());

Xnip2022-07-11_09-05-46.jpg

Buffer与字符编码

当Buffer与字符串之间转换时,可指定字符编码,默认utf8。
Node.js目前支持的字符编码如下:

  • utf8 多字节的Unicode字符
  • utf16le 多字节Unicode字符,与utf8不同的是,字符串中的每个字符都使用2或4个字节进行编码。Node.js中仅支持小端序的变体
  • latin1 代表ISO-8859-1,仅支持U+0000~U+00FF的Unicode字符。每个字符都使用单个字节进行编码,不符合规范的字符将被截断并映射到该范围内。(之前的名称是binary)

Xnip2022-07-06_09-24-04.jpg

Node.js还支持二进制转文本的编码:

  • hex 每个字节编码为两个16进制字符
  • base64 base64编码中的空白字符(空格、换行、制表)会被忽略
  • base64url v14.18.0,v15.7.0 新增

Xnip2022-07-06_09-06-52.jpg hex:字符 -> buffer时,如果字符串不完全由偶数个十六进制的字符组成,会发生数据截断

Xnip2022-07-06_09-10-32.jpg

内存分配

Nodejs内存

在了解buffer的内存分配前,我们先了解一下nodejs的内存

    let mem = process.memoryUsage();
    console.log(mem);
    // 结果是,单位都是字节
       {
            rss: 193200128,
            heapTotal: 173047808,
            heapUsed: 170937264,
            external: 942169,
            arrayBuffers: 9386
        }
  • rss 驻留集
    即进程在主内存设备(进程的内存还可能存在交换区或文件系统中)占用的空间量,包括所有C++和JavaScript的对象和代码
  • heapTotal
    V8堆内存,堆中总共申请的内存量
  • heapUsed
    V8堆内存,总重已使用的内存量
  • external
    绑定到v8管理的js对象的C++对象的内存使用量
  • arrayBuffers
    为ArrayBuffer和SharedArrayBuffer分配的内存,包括所有的nodejs buffer。这也包含在external值中。当nodejs作为嵌入库时,此值可能是0,这种情况下可能不会跟踪ArrayBuffer的分配
// 准备一个打印进程内存使用情况的函数
function showMemory() {
    const format = (bytes) => {
        return (bytes / 1024 / 1024).toFixed(2) + ' MB';
    };
    let mem = process.memoryUsage();
    let str = 'Process memory\n ';
    Object.keys(mem).forEach(key => {
        str += key + ':' + format(mem[key])+ '  ';
    });
    console.log(str);
}
showMemory();

Xnip2022-07-08_09-23-40.jpg

Buffer内存

1、node.js中的内存有两种,一是堆内存(由V8申请的),一是堆外内存(C++申请的)。buffer对象的内存是堆外内存。

function useMemory() {
    let size = 9 * 1024 * 1024; // 9MB
    let buffer = Buffer.alloc(size, 0);
    return buffer;
}

let total = [];

for(let i = 0; i < 5; i++) {
    console.log('i = ' + i);
    showMemory();
    total.push(useMemory());
}

Xnip2022-07-10_15-55-52.jpg 可以看到,rss、heapTotal、heapUsed变化极小,而external、arrayBuffers都是9MB递增。

2、Nodejs以8kb做为分界线来区分buffer是小对象还是大对象。
SlowBuffer是C++中定义的类,C++中分配Buffer对象内存的实际指向对象,可通过buffer.parent访问到。

1)小Buffer对象
多个buffer对象共用一个slab,所有小buffer对象都回收时,slab 8kb的空间才会被回收。 (slab单元指向SlowBuffer)

Xnip2022-07-08_09-32-58.jpg 这两个Buffer对象的parent是同一个 Xnip2022-07-10_16-46-34.jpg buffer释放前后相差8kb(左右)

2)大Buffer对象
直接分配一个SlowBuffer对象作为slab单元,这个slab单元会被这个大buffer对象独占。

参考文章