Buffer 是什么?
Buffer 作为存在于全局对象上, 无需引入模块即可使用, 你绝对不可以忽略它. 可以理解 Buffer 是在内存中开辟的一片区域, 用于存放二进制数据. Buffer 所开辟的是堆外内存.
Buffer 的应用场景有哪些?
流
怎么理解流呢? 流是数据的集合(与数据, 字符串类似), 但是流的数据不能一次性获取到, 数据也不会全部 load 到内存中, 因此流非常适合大数据处理以及断断续续返回 chunk 的外部源. 流的生产者与消费者之间的速度通常是不一致的, 因此需要 buffer 来暂存一些数据. buffer 大小通过 highWaterMark 参数指定, 默认情况下是 16Kb.
存储需要占用大量内存的数据
Buffer 对象占用的内存空间是不计算在 Node.js 进程内存空间限制上的, 所以可以用来存储大对象, 但是对象的大小还是有限制的. 一般情况下 32 位系统大约是 1G,64 位系统大约是 2G.
如何创建 Buffer
除了流自动隐式创建 Buffer 之外, 也可以手动创建 Buffer, 方式如下:
Buffer 中存储的数据已确定
Buffer.from(obj) // obj 支持的类型 string, buffer, arrayBuffer, array, or array-like object
注意: Buffer.from 不支持传入数字, 如下所示:
- Buffer.from(1234);
- buffer.js:208
- throw new errors.TypeError(
- ^
- TypeError [ERR_INVALID_ARG_TYPE]: The "value" argument must not be of type number. Received type number
- at Function.from (buffer.js:208:11)
- ...
若要传入数字可以采用传入数组的方式:
- const buf = Buffer.from([1, 2, 3, 4]);
- console.log(buf); // <Buffer 01 02 03 04>
但是这种方式存在一个问题, 当存入不同的数值的时候 buffer 中记录的二进制数据会相同, 如下所示:
- const buf2 = Buffer.from([127, -1]);
- console.log(buf2); // <Buffer 7f ff>
- const buf3 = Buffer.from([127, 255]);
- console.log(buf3); // <Buffer 7f ff>
- console.log(buf3.equals(buf2)); // true
当要记录的一组数全部落在 0 到 255(readUInt8 来读取)这个范围, 或者全部落在 - 128 到 127(readInt8 来读取)这个范围那么就没有问题, 否则的话就强烈不推荐使用 Buffer.from 来保存一组数. 因为不同的数字读取时应该调用不同的方法.
Buffer 存储数据未确定
Buffer.alloc,Buffer.allocUnsafe,Buffer.allocUnsafeSlow
Buffer.alloc 会用 0 值填充已分配的内存, 所以相比后两者速度上要慢, 但是也较为安全. 当然也可以通过 --zero-fill-buffers flag 使 allocUnsafe,allocUnsafeSlow 在分配完内存后也进行 0 值填充.
node --zero-fill-buffers index.js
当分配的空间小于 4KB 的时候, allocUnsafe 会直接从之前预分配的 Buffer 里面 slice 空间, 因此速度比 allocUnsafeSlow 要快, 当大于等于 4KB 的时候二者速度相差无异.
- // 分配空间等于 4KB
- function createBuffer(fn, size) {
- console.time('buf-' + fn);
- for (var i = 0; i <100000; i++) {
- Buffer[fn](size);
- }
- console.timeEnd('buf-' + fn);
- }
- createBuffer('alloc', 4096);
- createBuffer('allocUnsafe', 4096);
- createBuffer('allocUnsafeSlow', 4096);
- // 输出
- buf-alloc: 294.002ms
- buf-allocUnsafe: 224.072ms
- buf-allocUnsafeSlow: 209.22ms
- function createBuffer(fn, size) {
- console.time('buf-' + fn);
- for (var i = 0; i < 100000; i++) {
- Buffer[fn](size);
- }
- console.timeEnd('buf-' + fn);
- }
- createBuffer('alloc', 4095);
- createBuffer('allocUnsafe', 4095);
- createBuffer('allocUnsafeSlow', 4095);
- // 输出
- buf-alloc: 296.965ms
- buf-allocUnsafe: 135.877ms
- buf-allocUnsafeSlow: 205.225ms
需要谨记一点: new Buffer(xxxx) 方式已经不推荐使用了
Buffer 使用
buffer 转字符串
- const buf = Buffer.from('test');
- console.log(buf.toString('utf8')); // test
- console.log(buf.toString('utf8', 0, 2)); // te
buffer 转 json
- const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
- console.log(buf.toJSON()); // { type: 'Buffer', data: [ 1, 2, 3, 4, 5 ] }
buffer 裁剪, 裁剪后返回的新的 buffer 与原 buffer 指向同一块内存
buf.slice([start[, end]])
start 起始位置
end 结束位置(不包含)
示例:
- var buf1 = Buffer.from('test');
- var buf2 = buf1.slice(1, 3).fill('xx');
- console.log("buf2 content:" + buf2.toString()); // xx
- console.log("buf1 content:" + buf1.toString()); // txxt
buffer 拷贝, buffer 与数组不同, buffer 的长度一旦确定就不再变化, 因此当拷贝的源 buffer 比目标 buffer 大时只会复制部分的值
buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])
示例:
- var buf1 = Buffer.from('abcdefghijkl');
- var buf2 = Buffer.from('ABCDEF');
- buf1.copy(buf2, 1);
- console.log(buf2.toString()); //Abcdef
buffer 相等判断, 比较的是二进制值
buf.equals(otherBuffer)
示例:
- const buf1 = Buffer.from('ABC');
- const buf2 = Buffer.from('414243', 'hex');
- console.log(buf1.equals(buf2)); // true
除了 equals 之外, compare 其实也可以用于判断是否相等(当结果为 0 则相等), 不过 compare 更主要的作用是用于对数组内的 buffer 实例排序.
buffer 是否包含特定值
- buf.includes(value[, byteOffset][, encoding])
- buf.indexOf(value[, byteOffset][, encoding])
示例:
- const buf = Buffer.from('this is a buffer');
- console.log(buf.includes('this')); // true
- console.log(buf.indexOf('this')); // 0
写入读取数值
写入方法:
位数固定且超过 1 个字节的: write{Double| Float | Int16 | Int32| UInt16 | UInt32 }{BE|LE}(value, offset)
位数不固定的: write{Int | UInt}{BE | LE}(value, offset, bytelength) // 此方法提供了更灵活的位数表示数据(比如 3 位, 5 位)
位数固定是 1 个字节的: write{Int8 | Unit8}(value, offset)
读取方法:
位数固定且超过 1 个字节的: read{Double| Float | Int16 | Int32 | UInt16 | UInt32 }{BE|LE}(offset)
位数不固定的: read{Int | UInt}{BE | LE}(offset, byteLength)
位数固定是 1 个字节的: read{Int8 | Unit8}(offset)
Double,Float,Int16,Int32,UInt16,UInt32 既确定了表征数字的位数, 也确定了是否包含负数, 因此定义了不同的数据范围. 同时由于表征数字的位数都超过 8 位, 无法用一个字节来表示, 因此就涉及到了计算机的字节序区分(大端字节序与小端字节序)
关于大端小端的区别可以这么理解: 数值的高位在 buffer 的起始位置的是大端, 数值的低位 buffer 的起始位置则是小端
- const buf = Buffer.allocUnsafe(2);
- buf.writeInt16BE(256, 0)
- console.log(buf); // <Buffer 01 00>
- buf.writeInt16LE(256, 0)
- console.log(buf); // <Buffer 00 01>
https://tool.lu/hexconvert/ 这里可以查看数值的不同进制之间的转换, 如果是大端的话, 则直接按顺序 (0100) 拼接 16 进制即可, 如果是小端则需要调换一下顺序才是正确的表示方式.
buffer 合并
- Buffer.concat(list[, totalLength]) //totalLength 不是必须的, 如果不提供的话会为了计算 totalLength 会多一次遍历
- const buf1 = Buffer.from('this is');
- const buf2 = Buffer.from('funny');
- console.log(Buffer.concat([buf1, buf2], buf1.length + buf2.length));
- // <Buffer 74 68 69 73 20 69 73 20 66 75 6e 6e 79>
清空 buffer
清空 buffer 数据最快的办法是 buffer.fill(0)
buffer 模块与 Buffer 的关系
Buffer 是全局 global 上的一个引用, 指向的其实是 buffer.Buffer
- const buffer = require('buffer');
- console.log(buffer.Buffer === Buffer); //true
buffer 模块上还有其他一些属性和方法
- const buffer = require('buffer');
- console.log(buffer);
- { Buffer:
- { [Function: Buffer]
- poolSize: 8192,
- from: [Function: from],
- alloc: [Function: alloc],
- allocUnsafe: [Function: allocUnsafe],
- allocUnsafeSlow: [Function: allocUnsafeSlow],
- isBuffer: [Function: isBuffer],
- compare: [Function: compare],
- isEncoding: [Function: isEncoding],
- concat: [Function: concat],
- byteLength: [Function: byteLength],
- [Symbol(node.isEncoding)]: [Function: isEncoding] },
- SlowBuffer: [Function: SlowBuffer],
- transcode: [Function: transcode],
- INSPECT_MAX_BYTES: 50,
- kMaxLength: 2147483647,
- kStringMaxLength: 1073741799,
- constants: { MAX_LENGTH: 2147483647, MAX_STRING_LENGTH: 1073741799 } }
上面的 kMaxLength 与 MAX_LENGTH 代表了新建 buffer 时内存大小的最大值, 当超过限制值后就会报错
32 为机器上是(2^30)-1(~1GB)
64 位机器上是(2^31)-1(~2GB)
Buffer 释放
我们无法手动对 buffer 实例进行 GC, 只能依靠 V8 来进行, 我们唯一能做的就是解除对 buffer 实例的引用
参考资料
- http://cenalulu.github.io/linux/character-encoding/
- http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
- https://medium.freecodecamp.org/do-you-want-a-better-understanding-of-buffer-in-node-js-check-this-out-2e29de2968e8
- https://www.barretlee.com/blog/2017/06/06/dive-to-nodejs-at-stream-module/
- https://medium.freecodecamp.org/node-js-streams-everything-you-need-to-know-c9141306be93
- http://www.runoob.com/nodejs/nodejs-buffer.html
来源: https://juejin.im/post/5afd57e851882542ac7d76af