当前位置: 首页>移动开发>正文

Node中的Buffer 和 TypeArray,ArrayBuffer, Unit8Array

在看 Node 的 Buffer 模块文档时, 文档中提到这么一段话

Buffer实例也是Uint8Array实例。 但是与ECMAScript 2015中的 TypedArray 规范还是有些微妙的不同。 例如,当ArrayBuffer#slice()创建一个切片的副本时,Buffer#slice()的实现是在现有的Buffer上不经过拷贝直接进行创建,这也使得Buffer#slice()更高效
其中提到了 Unit8Array, TypedArray 以及 ArrayBuffer 这些类, 因此就想弄明白这些概念之间的关系是怎样的, 在看了 ES6的文档以及 Buffer 源代码后, 总算是理解了一些

TypedArray

首先要弄清楚 TypedArray 的概念, 这是 ES2015(又称ES6) 中新出的一个接口, 不能直接被实例化, 也就是说如下代码会报错.

new TypedArray()
因为这个接口就是一个抽象接口, 就像java中的抽象接口一样, 是不能被实例化的, 只能实例化实现该接口的子类. Uint8Array 就是实现 TypedArray 接口的一个子类.

就Nodejs而言, 可以使用Buffer操作二进制数据, 那对前端JS而言, 在 TypeArray 出现之前, 是没有可以直接操作二进制数据的类的, 这也与前端很少需要操作二进制数据相关.

所以 TypeArray 接口的作用是操作二进制数据.

TypeArray 是一个类数组结构, 也就是说数组可以用的函数, 比如 arr[0], slice, copy 等方法, TypeArray也可以使用.

ArrayBuffer

TypedArray 的作用是操作二进制数据, 其内部还有一个buffer属性, 这个buffer就是 ArrayBuffer 实例. ArrayBuffer 就存储了要操作的二进制数据.

因此可以知道, TypedArray 是一个操作二进制数据的接口, 内部的 ArrayBuffer 存储了要操作的二进制数据.

Unit8Array

实现了 TypedArray 接口的子类有很多, Unit8Array 就是其中一个, 这个子类表示: 数组中的每一个元素都是 8个二进制位(1个字节)的无符号整数.

无符号的含义是, 该二进制的首位不表示符号位. 而对于有符号的二进制位, 首位1表示负数. 如下实例:

二进制表示 10001111 00001111
无符号 143 15
有符号 -15(首位1表示负数) 15

所以 Unit8Array 中每个元素的取值范围应该是 0 ~ 255.

实现 TypedArray 接口的子类还有:

Int8Array: 每个元素是8个二进制位(1个字节)的有符号整数
Uint8Array: 每个元素是8个二进制位(1个字节)的无符号整数
Int16Array: 每个元素是16个二进制位(2个字节)的有符号整数
Uint16Array:每个元素是16个二进制位(2个??节)的无符号整数
Int32Array: 每个元素是32个二进制位(4个字节)的有符号整数
Uint32Array: 每个元素是32个二进制位(4个字节)的无符号整数

所以可以想到, 对于 Int8Array, 每个元素的取值范围应该是 -127 ~ 127.

对于3个元素的 Uint16Array 对象, 对应的字节长度为 3 * 2 = 6 个字节.

如下图:

TypedArray类型 要存储的数据16进制表示 数组长度 内部buffer(假设大端序存储)

Unit8Array 0x01 0x02 0x04 0x08 4(每个元素1字节) Buffer<0x01 0x02 0x04 0x08>(4字节)

Uint16Array 0x01 0x02 0x04 0x08 2(每个元素2字节) Buffer<0x01 0x02 0x04 0x08>(4字节)

Uint32Array 0x01 0x02 0x04 0x08 1(每个元素4字节) Buffer<0x01 0x02 0x04 0x08>(4字节)
存储同一个二进制数据, 使用三种 TypedArray 去存储, 内部 buffer 存储的数据都是一样的, 均占据4个字节(因为大端序和小端序会对字节存储顺序有影响, 这里假设都是大端序存储), 但是每种 TypedArray 外在表现的数组长度却是不一样的.

Buffer
最后就要提到 Buffer 这个类, Buffer 在前端JS中并不存在, 是 Node 专门提供用来操作二进制数据的, 因为对于后端来说, 操作二进制数据是比较基础的操作.

而在 ES6 的 TypeArray 推出之后, 自 Node 3.0.0 版本开始, Buffer 继承自 Unit8Array, 相当于是对 ES6 中的 TypeArray 做兼容.

那么到目前为止, 这些概念应该都很清楚了:

TypedArray: ES6 提供的用来操作二进制数据的接口, 具体由子类实现.

ArrayBuffer: 在 TypedArray 内部, 存储了要操作的二进制数据.

Unit8array: 实现 TypedArray, 每个元素都占据一个字节.

Buffer: Node中才有, 继承自 Unit8array, 拥有更多强大的二进制数据操作.

Buffer API 文档中提到的疑惑点解释
在弄明白 Unit8Array, TypedArray 以及 ArrayBuffer 和 Buffer 之间的相互关系后, 我们现在对 Buffer API 官方文档中提到的内容和一些示例做一些解释.

官方代码示例1:

// https://www.nodeapp.cn/buffer.html#buffer_buffers_and_typedarray
const arr = new Uint16Array(2);

arr[0] = 5000; 
arr[1] = 4000;

// 拷贝 `arr` 的内容
const buf1 = Buffer.from(arr);

// 与 `arr` 共享内存
const buf2 = Buffer.from(arr.buffer);

// 输出: <Buffer 88 a0>
console.log(buf1);

// 输出: <Buffer 88 13 a0 0f>
console.log(buf2);

这段代码仔细分析的话是存在几个疑惑点的,我们先看下 5000 和 4000的不同进制表示:

数字 二进制 16进制
5000 0001 0011 1000 1000 13 88
4000 0000 1111 1010 0000 0f a0

1.对于buf2而言, 为何打印的是 <Buffer 88 13 a0 0f> 而不是 <Buffer 13 88 0f a0> 呢

  1. 对于buf1而言, 为何长度是2而不是4, 打印的是 <Buffer 88 a0> 而不是 <Buffer 88 13> 呢?

对于第一个问题, 比较简单, 因为 Uint16Array 存储数据时使用的是小端序, 也就是每个元素中的字节顺序要反过来.

13 88 使用小端序存储时, 显示为 88 13

13 88 使用大端序存储时, 显示为 13 88

tip:关于大端序和小断序, 百度可以搜到很多讲解, 这里不细说.

因此, 打印的结果是 <Buffer 88 13 a0 0f>.

对于第二个问题, 我们必须得查看下Buffer的源码,

// https://github.com/nodejs/node/blob/v10.x/lib/buffer.js
from 函数 -> fromObject 函数 -> fromArrayLike 函数
function fromArrayLike(obj) { // 在我们的例子中, 这里obj就是 Uint16Array 对象
  const length = obj.length; // 得到新buffer的长度
  const b = allocate(length);
  for (var i = 0; i < length; i++)
    b[i] = obj[i]; // 设置buffer每个字节对应的数值 b[0] = 5000  b[1] = 4000
  return b;
}

可以看到 const length = obj.length; 这段代码, 获取的是 Uint16Array 对象的长度, 而不是 Uint16Array 对象内部 BufferArray 的长度, 所以最终返回的 buffer 的长度是2.

b[i]= obj[i]; 则是设置 buffer 每个字节的值, 那么根据这段设置, 返回的 buffer 中的元素应该是 [5000, 4000] 呀.

实际上, ??于Buffer对象而言, 每个字节的取值范围是???0~ 255 之间(想一下, Buffer继承自 Unit8Array), 对于 > 255的数, 不断减少256, 直到最终值在 0~ 255 之间, 对于 < 0 的数, 不断增加256, 直到最终值在 0~ 255 之间

tip: 如果看过深入浅出node.js , 应该知道里面也提过Buffer中每个元素的取值计算

所以:

b[0] = 5000 => b[0] = 5000 % 256 = 136 对应16进制表示为 88

b[0] = 4000 => b[1] = 4000 % 256 = 160 对应16进制表示为 a0

那么第二个问题也弄清楚了, Buffer.from(TypedArray), 返回的新buffer长度就是TypedArray的长度, buffer中每个字节值就是 TypedArray中每个元素经过计算后的值.

官方文档内容1:

注意,Buffer模块会预分配一个大小为Buffer.poolSize的内部Buffer实例作为快速分配池, 用于使用Buffer.allocUnsafe()新创建的Buffer实例,以及废弃的new Buffer(size)构造器, 仅限于当size小于或等于Buffer.poolSize >> 1(Buffer.poolSize除以2后的最大整数值

也就是说 只有使用 Buffer.allocUnsafe() 和废弃的 new Buffer 创建buffer对象时, 如果buffer的大小小于4KB, 则使用内部的Buffer实例作为快速分配池, 而不是申请新的内存.

如果看过朴灵的 深入浅出node.js, 就会知道文章中也这么提到过:

为了高效的使用分配的内存,Node采用了slab分配机制。如果指定的Buffer的大小小于8KB, 那么Node就会按照小对象的方式进行分配, 不为该buffer申请新的内存, 而是直接使用分配池中的内存.

两者的描述基本都一致, 不过深入浅出node.js 这本书推出时, 还是使用的 new Buffer 创建buffer对象, 不过自Node5.10.0开始, 因为安全性, 使用 new Buffer 创建buffer的方式已经弃用了, 取而代之的是使用 from, alloc 和 allocUnsafe 等函数来创建buffer对象,

tip: 具体可查看翻译的文档 Porting to Buffer.from()/Buffer.alloc() API

快速分配池这种方式理解起来蛮简单的:

就是一个buffer长度小于4KB时, 直接使用内部一个Buffer的内存, 因此, 会存在几个buffer对象的parent都是同一个对象这种情况, 如下来自深入浅出node.js的截图:

Node中的Buffer 和 TypeArray,ArrayBuffer, Unit8Array,第1张

空的快速分配池

分配一个1024 bit (128Byte)的buffer1

分配一个4000 bit (500Byte)的buffer2
buffer1 和 buffer 2 的 parent 都指向同一个快速分配池

当然, 现在弃用了buffer.parent的叫法, 改为了buffer.buffer.

TypedArray中的buffer属性就是 BufferArray对象, Unit8Array 实现了 TypedArray, Buffer又继承自 Unit8Array,所以 buffer.buffer 就是一个 ArrayBuffer对象.

直接看下 Buffer 源码中的 allocUnsafe函数, 验证下文档内容:

// https://github.com/nodejs/node/blob/68a6b8d3751bcaa96f32bdb843f171a098f1cbdb/lib/buffer.js#L79
// allocUnsafe => allocate
function allocate(size) {
  if (size <= 0) {
    return new FastBuffer();
  }
  if (size < (Buffer.poolSize >>> 1)) { // Buffer.poolSize 默认 = 8KB
    if (size > (poolSize - poolOffset)) // 快速分配池大小 - 快速分配池已分配的内存
      createPool(); // 快速分配池剩下空间不足时, 创建新的快速分配池
    var b = new FastBuffer(allocPool, poolOffset, size); // 使用快速分配池的内存
    poolOffset += size; // 更新快速分配池以分配的内存
    alignPool();
    return b;
  } else {
    return createUnsafeBuffer(size); // 不适用快速分配池
  }
}

代码所显示的逻辑和文档中提到的一致.

参考:

深入浅出node.js

Node官方文档 Buffer

TypedArray


https://www.xamrdz.com/mobile/45j1994146.html

相关文章: