Laya Byte

  • Post author:
  • Post category:其他


二进制数组在 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 获取当前主机的字节序



版权声明:本文为JunChow520原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。