ArrayBuffer 实践
ArrayBuffer
对象、TypedArray
视图和DataView
视图是 JavaScript 操作二进制数据的一个接口。都是以数组的语法处理二进制数据,所以统称为二进制数组。主要用于二进制数据的通信。
ArrayBuffer 以数组的形式直接操作内存,提升了脚本的性能。二进制数组由三类对象组成。
- ArrayBuffer 对象
- TypedArray 视图
- DataView 视图
ArrayBuffer 对象使用
ArrayBuffer
对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图(TypedArray
视图和DataView
视图)来读写,视图的作用是以指定格式解读二进制数据。
注意: ArrayBuffer 不能直接读写,必须通过 TypedArray 或 DataView才能进行读写。
// 创建 ArrayBuffer, 分配一段连续内存区域, 默认值为0
const buf = new ArrayBuffer(32);
// 获取 ArrayBuffer 长度
console.log(buf.byteLength); // 32
// ArrayBuffer 拷贝
const newBuffer = buf.slice(0, 3); // 从 0 ~ 3(不包括3拷贝)
// 判断是否是视图
const buffer = new ArrayBuffer(8);
ArrayBuffer.isView(buffer) // false
TypedArray 视图
同一段内存,不同数据有不同的解读方式,这就叫做“视图”(view)。
ArrayBuffer
有两种视图,一种是TypedArray
视图,另一种是DataView
视图。前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。
目前,TypedArray
视图一共包括 9 种类型,每一种视图都是一种构造函数。
Int8Array
:8 位有符号整数,长度 1 个字节。Uint8Array
:8 位无符号整数,长度 1 个字节。Uint8ClampedArray
:8 位无符号整数,长度 1 个字节,溢出处理不同。Int16Array
:16 位有符号整数,长度 2 个字节。Uint16Array
:16 位无符号整数,长度 2 个字节。Int32Array
:32 位有符号整数,长度 4 个字节。Uint32Array
:32 位无符号整数,长度 4 个字节。Float32Array
:32 位浮点数,长度 4 个字节。Float64Array
:64 位浮点数,长度 8 个字节。
// 创建 TypedArray
const buffer = new ArrayBuffer(8);
const typed = new Int32Array(buffer);
// 普通数组的操作方法和属性,对 TypedArray 数组完全适用。
let ui8 = Uint8Array.of(0, 1, 2);
for (let byte of ui8) {
console.log(byte);
}
// 0 1 2
// BYTES_PER_ELEMENT属性,表示数据类型占据的字节数。
Int8Array.BYTES_PER_ELEMENT // 1
Uint8Array.BYTES_PER_ELEMENT // 1
Uint8ClampedArray.BYTES_PER_ELEMENT // 1
// buffer属性,返回整段内存区域对应的ArrayBuffer对象。该属性为只读属性。
const a = new Float32Array(64);
const b = new Uint8Array(a.buffer);
// byteLength属性返回 TypedArray 数组占据的内存长度,单位为字节。
// byteOffset属性返回 TypedArray 数组从底层ArrayBuffer对象的哪个字节开始。这两个属性都是只读属性。
// length属性表示 TypedArray 数组含有多少个成员。注意将 length 属性是成员长度
const a = new Int16Array(8);
a.length // 8
a.byteLength // 16
字节序
字节序指的是数值在内存中的表示方式。涉及到大端对齐和小端对齐。
- 小端对齐采用小端字节序。比如,一个占据四个字节的 16 进制数
0x12345678
,决定其大小的最重要的字节是“12”,最不重要的是“78”。小端字节序将最不重要的字节排在前面,储存顺序就是78563412
; - 大端对齐采用大端字节序。大端字节序则完全相反,将最重要的字节排在前面,储存顺序就是
12345678
。 - 目前所有个人电脑几乎都是小端字节序,所以 TypedArray 数组内部也采用小端字节序读写数据。
- 对于采用大端对齐的操作系统,TypedArray 数组将无法正确解析。因此需要引入可以设置字节序的
DataView
案例:
buffer: [0x02, 0x01, 0x03, 0x07]
转变为小端对齐结果: 0x07030102
转变为大端对齐结果: 0x02010307
小端对齐: 0xff050210
转变为buffer结果: [0x10, 0x02, 0x05, 0xff]
大端对齐: 0xff050210
转变为buffer结果: [0xff, 0x05, 0x02, 0x10]
判断当前视图是小端字节序,还是大端字节序
const BIG_ENDIAN = Symbol('BIG_ENDIAN');
const LITTLE_ENDIAN = Symbol('LITTLE_ENDIAN');
function getPlatformEndianness() {
let arr32 = Uint32Array.of(0x12345678);
let arr8 = new Uint8Array(arr32.buffer);
switch ((arr8[0]*0x1000000) + (arr8[1]*0x10000) + (arr8[2]*0x100) + (arr8[3])) {
case 0x12345678:
return BIG_ENDIAN;
case 0x78563412:
return LITTLE_ENDIAN;
default:
throw new Error('Unknown endianness');
}
}
复合视图
视图的构造函数可以指定起始位置和长度,所以在同一段内存之中,可以依次存放不同类型的数据,这叫做“复合视图”。
const buffer = new ArrayBuffer(24);
// 将 ArrayBuffer 分成三个部分:
const idView = new Uint32Array(buffer, 0, 1);
const usernameView = new Uint8Array(buffer, 4, 16);
const amountDueView = new Float32Array(buffer, 20, 1);
DataView 视图
如果一段数据包括多种类型(比如服务器传来的 HTTP 数据),这时除了建立ArrayBuffer
对象的复合视图以外,还可以通过DataView
视图进行操作。DataView
视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。
// 构造函数
const dataView = new DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]);
// 长度属性
console.log(dataView.buffer); // 返回对应的 ArrayBuffer 对象
console.log(dataView.byteLength); // 返回占据的内存字节长度
console.log(dataView.byteOffset); // 返回当前视图从对应的 ArrayBuffer 对象的哪个字节开始
DataView
实例提供 8 个方法读取内存。用 set
方法写入内存
getInt8
:读取 1 个字节,返回一个 8 位整数。getUint8
:读取 1 个字节,返回一个无符号的 8 位整数。getInt16
:读取 2 个字节,返回一个 16 位整数。getUint16
:读取 2 个字节,返回一个无符号的 16 位整数。getInt32
:读取 4 个字节,返回一个 32 位整数。getUint32
:读取 4 个字节,返回一个无符号的 32 位整数。getFloat32
:读取 4 个字节,返回一个 32 位浮点数。getFloat64
:读取 8 个字节,返回一个 64 位浮点数。
应用
AJAX
let xhr = new XMLHttpRequest();
xhr.open('GET', someUrl);
// 设置返回值类型为二进制数据类型,如果不知道类型,可以设为 blob
xhr.responseType = 'arraybuffer';
// xhr.readyState == 4 时执行
xhr.onload = function () {
// 回传32 位整数时处理
const arrayResponse = xhr.response;
const dataView = new DataView(arrayResponse);
const ints = new Uint32Array(dataView.byteLength / 4);
xhrDiv.style.backgroundColor = "#00FF00";
xhrDiv.innerText = "Array is " + ints.length + "uints long";
};
xhr.send();
WebSocket
WebSocket
可以通过ArrayBuffer
,发送或接收二进制数据。
let socket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';
// 等待 socket 连接创建
socket.addEventListener('open', function (event) {
// 发送二进制数据
const typedArray = new Uint8Array(4);
socket.send(typedArray.buffer);
});
// 接收二进制数据
socket.addEventListener('message', function (event) {
const arrayBuffer = event.data;
// ···
});
将 ArrayBuffer 中数据转 utf-8
// 0x4F60;0x597D;0x554A; => 你好啊
const array = new ArrayBuffer(6);
const type = new Int16Array(array);
type[0] = 0x4F60;
type[1] = 0x597D;
type[2] = 0x554A;
let str = '';
for(let c of type) {
str += String.fromCharCode(c);
}
console.log(str);