一. 前言
日常的开发工作中,或多或少都会接触到一些高性能,大数据处理相关的场景(3D渲染、WebGL
),这时候用传统的js来处理这些数据就会显的捉襟见肘。我们前端开发迫切的需要一种新的方法
,新的技术手段
来处理对应的问题,二进制数组
就是一个不错的选择。
正好自己这段时间在搞大文件上传的问题,顺带了解了一下二进制数组
相关知识。这里分享一下哈!有问题的地方,欢迎各位jym
指出!

二. 简介
2.1 诞生流程
简单点来说,二进制数组是js操作二进制数据
的接口。
传统的js处理数据是文本格式
,这会牵涉到一个性能问题,如果与显卡等底层驱动数据交互
的时候,性能就会很差。
为了满足js
和显卡
之间的大量
数据实时
通信,两者之间的通信必须是 二进制
(二进制在传输过程中不像传统文本格式(XML、JSON)需要复杂的转码过程)。在这个过程中,JS需要提供一个接口,可以直接操作二进制数据
,保证操作前后数据格式
不发生变化。这种流程下,JS脚本的性能将会大大提高。
2.2 类型
二进制数组由三种对象构成:
ArrayBuffer
对象 :是JS
中用于表示固定长度二进制数据的底层对象
,创建后无法调整大小,需通过视图
进行读写
操作。
这里必须要了解的一点: ArrayBuffer
对象作为内存区域可以存放多种类型
的数据。不同的数据类型有不同的解读方式
。ArrayBuffer有两种类型
的视图,一种是类型化数组视图(TypedArray
),另一种是数据视图(dataView
)。
TypedArray
视图 : 解读的类数组类型必须为同一类型
(可以看为简单类型
)。DataView
视图 :可以解读不同类型
的类数组(可以看为复杂类型
)。
下面这些是TypedArray
和DataView
支持的数据类型。


PS:
- 除了一些3D场景,常见的
Canvas、File API、XHR
等也用到了二进制数组。 - 二进制数组其实不是数组,它是类数组对象。
三.API介绍和使用
3.1 ArrayBuffer对象
3.1.1 介绍
ArrayBuffer代表的是存储二进制数据的一段内存
ArrayBuffer是一个构造函数
,通过new关键词创建。可以分配一块连续的内存用于存储二进制数据
。
js
const buffer = new ArrayBuffer(32) // 分配一个32字节的内存。每个字节都默认为0

这里注意一下:分配的单位是字节
。
3.1.2 方法
ArrayBuffer.prototype.byteLength
ArrayBuffer
实例的byteLength
属性,返回所分配的内存区域的字节长度。
js
const buffer = new ArrayBuffer(32);
buffer.btyeLength // 32
当我们想要分配一段很大的内存空间
时,可以用来检测是否分配成功。
js
const buffer = new ArrayBuffer(n)
if (n === buffer.btyeLength) {
// success
}
ArrayBuffer.prototype.slice()
slice方法,允许对ArrayBuffer分配的内存进行切割截取
。然后形成一个新的ArrayBuffer对象
。
js
const buffer1 = new ArrayBuffer(32)
const buffer2 = buffer1.slice(0, 4) // 从 0 开始, 到 3 结束。(不包含末尾)
console.log(buffer1)
console.log(buffer2)

可以看出,slice并不会改变原始ArrayBuffer
分配的内存。
slice方法,是开辟了一个新的内存空间,然后讲截取的部分复制过去。
3.2 TypedArray视图
上面介绍到了,视图就是对ArrayBuffer开辟的内存进行解读操作
。同一段内存,因为数据类型的不同,有不同种的解读方式
。
TypedArray
视图,要保证ArrayBuffer
中的成员都是同一类型
。
3.2.1 介绍
目前,市面上TypedArray
视图一共包括以下几种类型(比较常用的)。
Int8Array
:8位有符号整数
,length === 1(字节
)Unit8Array
:8位无符号整数
, length === 1(字节
)Uint8ClampedArray
:8位无符号整数
,length === 1(字节
),溢出处理不同Int16Array
:16位有符号整数
,length === 2(字节
)Uint16Array
:16位无符号整数
,length === 2(字节
)Int32Array
:32位有符号整数
,length === 4(字节
)Uint32Array
:32位无符号整数
,length === 4(字节
)Float32Array
:32位浮点数
,length === 4(字节
)Float64Array
:64位浮点数
,length === 8(字节
)
通过这些构造函数
生成的数组
,统一称之为TypedArray视图
。这些数组有以下几个特性:
- 本身不存储数据。
- 内部类型都是一种。
- 数组成员是连续的。
- 数组成员大小默认都为0
3.2.2 用法
TypedArray(buffer, start, length)
- start:开始字节位置
- length:字节长度
举几个例子:
js
const buffer = new ArrayBuffer(32);
const t1 = new Int32Array(buffer); // 创建一个指向buffer的int32类型视图,开始位置默认为0,长度整个buffer

js
const t2 = new Int32Array(buffer, 0, 4) // 开始于字节0,长度为4

js
const t2 = new Int32Array(buffer, 4, 4) // 开始于字节4,长度为4

除了上面这些用法,构造函数
还可以直接接受数,生成TypedArray
实例。
javascript
const t5 = new Uint8Array([255, 255, 255, 255]); // 把一个普通数组,变成8位无符号整数的TypedArray实例

如果你经常弄Canvas的,话这种结构应该很熟悉!
我们操作TypedArray数组的话,方法和数组方法基本是通用的
。下面写一个简单的附值的例子。
js
const buffer = new ArrayBuffer(8);
const t1 = new Uint8Array(buffer);
t1[0] = 255;
t1[1] = 255;
// [255, 255, 0, 0, 0, 0, 0, 0]
对于这种TypeArray数组,我们也可以转为真正的数组
。
js
Array.from(t1)
Array.prototype.slice.call(t1)

同时,TypeArray数组也部署了Iterator接口
。支持使用for...of遍历
我们一层一层点开它的原型,确实有。


ps:注意一下,TypedArray数组没有concat方法
!
TypedArray.prototype.buffer
TypedArray实例的buffer
属性,返回整段内存区域对应的ArrayBuffer
对象。该属性为只读属性。
3.2.3 BYTES_PER_ELEMENT属性
每一种视图的构造函数,都有一个BYTES_PER_ELEMENT
属性,表示这种数据类型占据的字节。
js
Int8Array.BYTES_PER_ELEMENT // 1
Uint8Array.BYTES_PER_ELEMENT // 1
Int16Array.BYTES_PER_ELEMENT // 2
Uint16Array.BYTES_PER_ELEMENT // 2
Int32Array.BYTES_PER_ELEMENT // 4
Uint32Array.BYTES_PER_ELEMENT // 4
Float32Array.BYTES_PER_ELEMENT // 4
Float64Array.BYTES_PER_ELEMENT // 8
3.2.4 边界和溢出
每种视图的构造函数都有固定的范围
。比如Unit8Array
,范围就是0~255
。超过边界就会溢出。
js
const t = new Uint8Array([255, 256, 0, 0]) // [255, 0, 0, 0]
// 这里的256就超过了范围。
从网上找到一张表,这里面写的很详细。

3.3 字节序
3.3.1 介绍
其实对于前端开发,字节序的了解真的不多。字节序指的是数值在内存中的表示方式
。
js
let buffer = new ArrayBuffer(16);
let int32View = new Int32Array(buffer);
上面这个代码,生成了一个16字节的内存(ArrayBuffer对象)
。然后在他基础上创建一个Int32Array
,带符号的32位整数视图,每个32位整数占据 32 / 8 = 4个字节
。所以一共可以写入4个32位整数
。
我们往里面写入一点数据:
js
int32View[0] = 0
int32View[1] = 2
int32View[2] = 4
int32View[3] = 6 // Int32Array(4) [0, 2, 4, 6]
这时候我们用Int16Array去创建视图,结果完全不同。
js
var int16View = new Int16Array(buffer);
// Int16Array(8) [0, 0, 2, 0, 4, 0, 6, 0]
因为是16位整数视图,所以每个16位占 16 / 8 = 2 个字节
。一共可以写入8个16位整数
。为什么读出来的结果是2在前
,6在后
呢?因为x86体系
的计算机都采用小端字节序
(little endian)
3.3.2 小端字节序
相对重要的字节排在后面的内存地址,相对不重要字节排在前面的内存地址


对于我们前端而言,TypedArray就是小端,DataView大端小端都可以自行设计
。
3.4 DataView视图
当大量数据出现不同的时候,DataView
这种视图就可以使用上了。
3.4.1 介绍
简单使用,其实类似于TypedArray
。
js
const buffer = new ArrayBuffer(24);
const dv = new DataView(buffer);

3.4.2 使用
DataView视图,通过以下几种方法读取和写入内存。
读取内存
:
getInt8
:读取1个字节,返回一个8位整数。getUint8
:读取1个字节,返回一个无符号的8位整数。getInt16
:读取2个字节,返回一个16位整数。getUint16
:读取2个字节,返回一个无符号的16位整数。getInt32
:读取4个字节,返回一个32位整数。getUint32
:读取4个字节,返回一个无符号的32位整数。getFloat32
:读取4个字节,返回一个32位浮点数。getFloat64
:读取8个字节,返回一个64位浮点数。
写入内存
:
setInt8
:写入1个字节的8位整数。setUint8
:写入1个字节的8位无符号整数。setInt16
:写入2个字节的16位整数。setUint16
:写入2个字节的16位无符号整数。setInt32
:写入4个字节的32位整数。setUint32
:写入4个字节的32位无符号整数。setFloat32
:写入4个字节的32位浮点数。setFloat64
:写入8个字节的64位浮点数。
这个DataView,我确实用的不多,这里就不过多描述了。有时间多研究研究。
四. 使用场景
4.1 Canvas
Canvas元素设计到处理色彩rgb等问题,都是要处理内部二进制数组的。
js
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
这里这个imageData就是二进制数组。
4.2 WebSocket
WebSocket也可以传输二进制数据。
js
let ws = new WebSocket('ws://127.0.0.1:8080');
ws.binaryType = 'arraybuffer'; // 设置类型
// 链接打开,发送二进制数据
ws.addEventListener('open', () => {
let t1 = new Uint8Array(4);
ws.send(t1.buffer);
})
ws.addEventListener('message', function (event) {
let arrayBuffer = event.data; // 获取数据
});
4.3 File
File可以通过readAsArrayBuffer
读取二进制文件。
js
const up = document.getElementById('input');
const file = up.files[0];
const reader = new FileReader(); // 读取对象
reader.readAsArrayBuffer(file); //