二进制数组在 JavaScript 中其实很早就存在,在 WebGL 中为了高效地和显卡进行数据交换,ES6 为浏览器引入了 TypedArray 和 DataView 两个操作底层二进制数据的视图。因为具有直接操作内存的能力而不用进行转换,在处理 WebGL 二进制数据上性能远高于 Array。
JavaScript中将二进制数据分为三部分:
类型 | 名称 | 描述 | 设计目的 |
---|---|---|---|
ArrayBuffer | 数组缓冲区 | 代表内存中一段二进制数据 | - |
TypedArray | 类型化数组 | 代表读写简单的二进制数据 | 本机数据传输 |
DataView | 数据视图 | 代表读写复杂的二进制数据 | 网络数据传输 |
在设计目的上,ArrayBuffer对象的各种类型化视图TypedArray是用来向网卡、声卡之类的本机设备传送数据,所以本机的字节序就可以了。而DataView的设计目的是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。DataView视图提供更多操作选项,而且支持设定字节序。
TypedArray 类型化数组与 DataView 数据视图只是底层数据缓冲区的视图,若缓冲区的数据发生变化,视图也会改变。
数据类型 | 字节长度 | 含义 | C | TypedArray | DataView |
---|---|---|---|---|---|
Int8 | 1Byte 8Bit | 8位有符号整数 | char | Int8Array | Int8 |
Uint8 | 1Byte 8Bit | 8位无符号整数 | unsigned char | Uint8Array | Uint8 |
Uint8C | 1Byte 8Bit | 8位无符号整数(自动过滤溢出) | unsigned char | Uint8ClampedArray | 不支持 |
Int16 | 2Byte 16Bit | 16位有符号整数 | short | Int16Array | Int16 |
Uint16 | 2Byte 16Bit | 16位无符号整数 | unsigned short | Uint16Array | Uint16 |
Int32 | 4Byte 32Bit | 32位有符号整数 | int | Int32Array | Int32 |
Uint32 | 4Byte 32Bit | 32位无符号整数 | unsigned int | Uint32Array | Uint32 |
Float32 | 4Byte 32Bit | 32位浮点数 | float | Float32Array | Float32 |
Float64 | 8Byte 64Bit | 64位浮点数 | double | Float64Array | Float64 |
ArrayBuffer
JavaScript中的数值的类型都是使用 Float64 进行存储的,在内存中占据 8 字节 64 位。如果只存储 1 这个数值,同样需要申请 8字节的空间,这相当于浪费了 63Bit。
ArrayBuffer 的作用是创建一个二进制数据的缓冲区,创建之后不能直接对缓冲区进行操作,必须构建视图,通过视图对数据进行操作。
ArrayBuffer 并非一个新的数据类型而是新的的接口,通过构造函数可以返回一段包含指定字节数量的内存地址。
ArrayBuffer 代表内存中的一段二进制数据,在创建缓冲区时时按字节位为单位。
属性 | 描述 |
---|---|
byteLength | 内存区域的字节长度 |
const n = 32;
let buf = new ArrayBuffer(n);
if(buf.byteLength != n){
console.log("内存空间分配失败");
}
ArrayBuffer提供的slice()方法用于分配新内存
方法 | 描述 |
---|---|
slice(start=0, end=this.byteLength) | 分配新内存,将原内存start到end部分复制,返回此段新内存区域。 |
let buf = new ArrayBuffer(32);
let newbuf = buf.slice(0, 3);
ArrayBuffer代表内存中的一段二进制数据,它是无法直接操作的,需要使用视图(TypedArray或DataView)按照一定格式对其进行解读。
构造一段内存来存放二进制数据,需要注意的是由于内存是有限的稀缺资源,所以在分配内存空间是需要根据实际情况来处理。
//分配32字节的内存空间用来存放数据,默认全部位0。
let buf = new ArrayBuffer(32);
//将内存空间转换为视图
let dataview = new DataView(buf);
//获取第一个字节的值
dataview.getUint8(0);
无论使用哪一种视图,实例化的内存如果是共享的,则所有写入操作会修改每个视图。
let buf = new ArrayBuffer(32);
let u8 = new Uint8Array(buf);
let u16 = new Uint16Array(buf);
console.log(u8[0]);
u16[0] = -1;
console.log(u8[0]);
TypedArray
TypedArray 类型化数组具有一个DataView()的构造函数并接收一个ArrayBuffer数组缓冲区作为参数,用于视图化该内存区域。
// TypedArray 类型化数组构造函数的参数
Constructor(buffer, start = 0, length = buffer.byteLength - start * 8)
TypedArray 也可以接收一个数组参数,实例化该数组为二进制内容,得到的值也是一个数组,可以直接数组访问符[]
获取每个索引位置的内容,该数组同样具有length
属性。
TypedArray 类型化数组与 DataView 数据视图的区别在于,TypedArray 类型化数组是特定类型的视图,实际上并没有一个构造器叫做TypedArray,其使用主要分为9各不同的类型。
TypedArray的构造函数有9种
数据类型 | 字节长度 | 含义 | C | TypedArray | DataView |
---|---|---|---|---|---|
Int8 | 1Byte 8Bit | 8位有符号整数 | char | Int8Array | Int8 |
Uint8 | 1Byte 8Bit | 8位无符号整数 | unsigned char | Uint8Array | Uint8 |
Uint8C | 1Byte 8Bit | 8位无符号整数(自动过滤溢出) | unsigned char | Uint8ClampedArray | 不支持 |
Int16 | 2Byte 16Bit | 16位有符号整数 | short | Int16Array | Int16 |
Uint16 | 2Byte 16Bit | 16位无符号整数 | unsigned short | Uint16Array | Uint16 |
Int32 | 4Byte 32Bit | 32位有符号整数 | int | Int32Array | Int32 |
Uint32 | 4Byte 32Bit | 32位无符号整数 | unsigned int | Uint32Array | Uint32 |
Float32 | 4Byte 32Bit | 32位浮点数 | float | Float32Array | Float32 |
Float64 | 8Byte 64Bit | 64位浮点数 | double | Float64Array | Float64 |
- TypedArray 类型化数组的不同构造函数对内存会进行不同位数的格式化,以得到对应类型值的数据。
- TypedArray类型化数组不同于普通数组,不支持稀疏数组,默认值为0。
- TypedArray类型化数组的同一个数组只能存放同一种类型的变量
例如:划分一块 ArrayBuffer 得到C语言中的结构体
// C语言结构体
struct User{
char name[16];
char gender;
int age;
float score;
}
let buf = new ArrayBuffer(24);
let name = new Uint8Array(buf, 0, 16);
let gender = new Uint8Array(buf, 16, 1);
let age = new Uint16Array(buf, 18, 1);
let score = new Float32Array(buf, 20, 1);
DataView
DataView数据视图是一个可以从ArrayBuffer数组缓冲区对象中读取多种数值类型的底层接口,使用时无需考虑不同平台的字节序问题。
DataView是一种通用视图,不仅仅可以对指定字节端进行读写,还可以在同一缓冲区内使用各不同类型的数据,以提高内存利用效率。
let buf = new ArrayBuffer(16);
// from byte 12 for the next 4 byte
let dv = new DataView(buf, 12, 4);
//put 32 in slot 12
dv.setInt8(12, 32);
// output
console.log(dv.getInt8(0));
DataView数据视图具有一个构造函数可接收一个ArrayBuffer数组缓冲区参数,用于视图化该段内存。
当一段内存拥有多种数据时,复合视图使用起来会不太方便,此时更适合使用DataView数据视图。由于DataView数据视图可以自定义高位优先和低位优先,因此可以读取的数据就更多了。
new DataView(buffer[, byteOffset[, byteLength]])
参数 | 描述 |
---|---|
buffer | 一个ArrayBuffer或SharedArrayBuffer对象,DataView对象的数据源。 |
byteOffset | DataView对象的第一个字节在buffer中的偏移量,若未指定则默认从第一个字节开始。 |
byteLength | DataView对象的字节长度,若未指定则默认与buffer的长度相同。 |
DataView 数据视图实例化后会返回一个DataView对象,用于呈现指定的缓冲区内数据。可以将返回的对象想象成一个二进制ArrayBuffer的解释器,它直到如何在读取或写入时正确地转换字节码,这意味着它能在二进制层面处理整数与浮点数转换、字节顺序等相关的细节问题。
DataView数据视图的构造函数的参数与TypedArray类型化数组一样
Constructor(buffer, start = 0, length = buffer.byteLength - start * 8);
DataView数据视图格式化读取ArrayBuffer数组缓冲区数据的方式
方法 | 描述 |
---|---|
getInt8(start, isLittleEndian=false) | 从start字节处开始读取1个字节并返回8位有符号整数,默认高位优先。 |
getUint8(start, isLittleEndian=false) | 从start字节处开始读取1个字节并返回8位无符号整数,默认高位优先。 |
getInt16(start, isLittleEndian=false) | 从start字节处读取2个字节并返回16位有符号整数,默认高位优先。 |
getUint16(start, isLittleEndian=false) | 从start字节处读取2个字节并返回16位无符号整数,默认高位优先。 |
getInt32(start, isLittleEndian=false) | 从start字节处读取4个字节并返回32位有符号整数,默认高位优先。 |
getUint32(start, isLittleEndian=false) | 从start字节处读取4个字节并返回32位无符号整数,默认高位优先。 |
getFloat32(start, isLittleEndian=false) | 从start字节处读取4个字节并返回32位浮点数,默认高位优先。 |
getFloat64(start, isLittleEndian=false) | 从start字节处读取8个字节并返回64位浮点数,默认高位优先。 |
格式化写入ArrayBuffer数组缓存数据
方法 | 描述 |
---|---|
setInt8(start, value, isLittleEndian=false) | 在start字节位置写入1个字节8位有符号整数value,默认高位优先。 |
setUint8(start, value, isLittleEndian=false) | 在start字节位置写入1个字节8位无符号整数value,默认高位优先。 |
setInt16(start, value, isLittleEndian=false) | 在start字节位置写入2个字节16位有符号整数value,默认高位优先。 |
setUint16(start, value, isLittleEndian=false) | 在start字节位置写入2个字节16位无符号整数value,默认高位优先。 |
setInt32(start, value, isLittleEndian=false) | 在start字节位置写入4个字节32位有符号整数value,默认高位优先。 |
setUint32(start, value, isLittleEndian=false) | 在start字节位置写入4个字节32位无符号整数value,默认高位优先。 |
setFloat32(start, value, isLittleEndian=false) | 在start字节位置写入4个字节32位浮点数value,默认高位优先。 |
setFloat64(start, value, isLittleEndian=false) | 在start字节位置写入8个字节64位浮点数value,默认高位优先。 |
如果不确定正在使用的计算机的字节序,可使用一下方式进行判断。
let isLittleEndian = (()=>{
let buf = new ArrayBuffer(2);
new DataView(buf).setInt16(0, 256, true);
return new Int16Array(buf)[0] === 256;
})();
Laya.Byte
Laya项目开发中对二进制的操作是不可或缺的,Laya的 Byte 在参考ActionScript3.0的二进制数组 ByteArray 的同时承接了H5的 TypedArray 类型化数组的特点。ByteArray 字节数组是二进制数据组成的序列,其中每个元素都是由 8Bit 二进制位组成。
Laya的Byte封装的其实就是H5的类型化数组,开发者可以参考MDN官方的API来进行扩展。
项目 | 描述 |
---|---|
Package | laya.utils |
Class | Laya.Byte |
Inheritance | Byte / Object |
Laya的Byte
类提供用于优化读取、写入以及处理二进制数据的方法和属性。Byte
类适用于需要在字节层访问数据的高级开发人员。
构造函数
// 创建一个字节类的实例
let byte = new Laya.Byte(data?:any);
构造函数参数 data
用于指定初始化的元素数量,或者用于初始化的 TypedArray
类型化数组对象、ArrayBuffer
对象。如果为 null
则预分配一定的内存空间,当可用空间不足时会优先使用此部分内存,如果仍然不够则会重新分配所需内存空间。
属性 | 描述 |
---|---|
BIG_ENDIAN | 主机字节序,大端字节序。 |
LITTLE_ENDIAN | 主机字节序,小端字节序。 |
主机字节序 Endian 是CPU存放数据的两种不同顺序,包括大端字节序 BIG_ENDIAN 和小端字节序 LITTLE_ENDIAN 。可以通过 getSystemEndian()
方法获取当前系统的字节序类型。
- 大端字节序
BIG_ENDIAN
地址低位存储值的高位,地址高位存储值的低位,又称为网络字节序。 - 小端字节序
LITTLE_ENDIAN
地址低位存储值的低位,地址高位存储值的高位。
存取器 | 描述 |
---|---|
buffer | 获取当前对象的ArrayBuffer数据,只包含有效数据部分。 |
endian | 字节实例的字节序类型 |
pos | 移动或返回字节对象的读写指针的当前位置 |
bytesAvailable | 从字节流的当前位置到末尾,读取的数据的字节流。 |
length | 字节对象的长度,以字节为单位。 |
方法 | 描述 |
---|---|
clear() | 清除字节数组的内容 |
getUTFBytes(len?:number):string | 从字节流中读取一个由length参数指定的长度的UTF-8字节序列并返回一个字符串,一般读取的是由writeUTFBytes方法写入的字符串。 |
getUTFString():string | 从字节流中读取一个UTF-8字符串,假定字符串的前缀是一个无符号的短整型,以此字节表示要读取的长度。对应的写入方法是writeUTFString |
readArrayBuffer(length:number):ArrayBuffer | 读取ArrayBuffer数据 |
readByte():number | 从字节流中读取待符号的字节,返回值的范围是从-128~127。 |
readFloat32():number | 从字节流的当前字节偏移位置读取一个IEEE754单精度32位浮点数 |
readFloat32Array(start:number, length:number):any | 从字节流中start参数指定的位置开始读取length参数指定的字节数的数据,用于创建一个Float32Array对象并返回此对象。 |
readFloat64():number | 从字节流的当前字节偏移量位置处读取一个IEEE754双精度64位浮点数 |
readInt16():number | 从字节流的当前字节偏移量位置处读取一个Int16值 |
readInt16Array(start:number, length:number):any | 从字节流中start参数指定的位置开始,读取length参数指定的字节数的数据,用于创建一个Int16Array对象并返回此对象。 |
readInt32():number | 从字节流的当前字节偏移量位置处读取一个Int32值 |
readString():string | 常用于解析固定格式的字节流,先从字节流的当前字节偏移位置处读取一个Uint16值,然后以此值为长度读取此长度的字符串。 |
readUint16():number | 从字节流的当前字节偏移量位置处读取一个Uint16值 |
readUint32():number | 从字节流的当前字节偏移量位置处读取一个Uint32值 |
readUint8():number | 从字节流的当前字节偏移量位置处读取一个Uint8值 |
readUint8Array(start:number, length:number):Uint8Array | 从字节流中start参数指定的位置开始读取length参数指定的字节数的数据用于创建一个Uint8Array对象并返回此对象。 |
writeArrayBuffer(arraybuffer:any, offset?:number, length?:number):void | 将指定arraybuffer对象中以offset为起始偏移量,length为长度的字节序列写入字节流。 |
writeByte(value:number):void | 在字节流中写入一个字节 |
writeFloat32(value:number):void | 在字节流的当前字节偏移量位置处写入一个IEEE754单精度32位浮点数 |
writeFloat64(value:number):void | 在字节流的当前字节偏移量位置处写入一个IEEE754双精度64位浮点数 |
writeInt16(value:number):void | 在字节流的当前字节偏移量位置处写入指定的Int16值 |
writeInt32(value:number):void | 在字节流的当前字节偏移量位置处写入指定的Int32值 |
writeUTFBytes(value:string):void | 将UTF-8字符串写入字节流 |
writeUTFString(value:string):void | 将UTF-8字符串写入字节流 |
writeUint16(value:number):void | 在字节流的当前字节偏移量位置处写入指定的Uint16值 |
writeUint32(value:number):void | 在字节流的当前字节偏移量位置处写入Uint32值 |
writeUint8(value:number):void | 在字节流的当前字节偏移量位置处写入指定的Uint8值 |
getSystemEndian():string | 获取当前主机的字节序 |