我们知道在C语言中,可以使用mallocfree方法来分配和释放内存。随着web的发展中,js在ES6中新增了内存操作的支持。其实现方式就是---typed array。

typed array是个集体的概念。int8Array,Uint8Array,int16Array,Uint16Array等统统等称为typed array。通过这些类,开发者可以方便地读写内存中的二进制数据。

typed array在内部设计时分成了两部分:bufferviewbuffer层表示内存中的数据块,view负责提供操作数据块的接口。

ArrayBuffer

buffer层的底层实现就是基于ArrayBuffer类。ArrayBuffer的功能与malloc类似,为用户分配一块内存。

创建一个ArrayBuffer实例很简单,它接收一个参数,参数表示要分配多大的一块内存区域,字节为单位。下面的代码片段,分配了一块8字节的内存区:

var buffer = new ArrayBuffer(8);

我们还可以通过byteLength访问该实例的内存大小:

console.log(buffer.byteLength) // 8

使用slice方法创建一个新的实例,其内容复制原ArrayBuffer实例中的部分内容。

var buf2 = buffer.slice(0,2);

创建一个新的实例,分配2字节大小的内存,其内容复制buffer中索引为0和1的内存中的数据。

但是ArrayBuffer并不提供对内存读写的方法。如果要对该内存块进行操作,需要用到另一个类DataView

DataView

DataView类提供了访问ArrayBuffer的接口。其语法如下:

new DataView(buffer [, byteOffset [, byteLength]])

首先接收一个必传的buffer对象,该对象就是DataView实例接下来将要操作的内存块。其次是一个可选的byteOffset,该参数表示DataView实例要操作的buffer对象的开始位置。最后的参数指定要操作的buffer对象中元素的个数。
下面的代码片段演示了DataView如何操作ArrayBuffer

var buffer = new ArrayBuffer(16);
var dv = new DataView(buffer, 0);

dv.setInt16(1, 42);
dv.getInt16(1); //42

示例中创建了一个DataView实例,该实例将buffer作为被操作的内存块,并且指定该内存块的16个字节都位于可操作区。
接着调用setInt16方法向从buffer的第2个字节开始存储带符号的16位整数。调用getInt16方法读出位于第2个字节处的带符号的16位整数。

typed array

如上所述,typed array的实现是组合了ArrayBufferDataView。也就是说typed array 通过ArrayBuffer创建内存块,通过DataView实现对内存块的读写操作。

typed array提供了多个类实现对同一块内存中的二进制数据按照不同的位数格式进行读写。如使用int8Array从内存中以带符号的8位整数的形式读写数据。

下面看一个例子来加深对typed array的理解

var buffer = new ArrayBuffer(16) //创建16b的内存区
var int32View = new Int32Array(buffer);
var int16View = new Int16Array(buffer);

for (var i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
  console.log(int32View[i]); // 0, 2, 4, 6
}

for (var i = 0; i < int16View.length; i++) {
  console.log(int16View[i]); // 0, 0, 2, 0, 4, 0, 6, 0
}

buffer是一个ArrayBuffer实例,表示一块16b的内存区。同时创建了两个typed array,分别是32位和16位的。区别在于使用32位的访问buffer时,会把buffer(16b)以32位为单位划分为4个元素,读写都是以32位为单位进行的。16位的进行访问时,会把buffer(16b)以16位为单位划分为8个元素,每次读写都是16位。

第一个循环中,我们使用int32Viewbuffer中的4个元素中分别写入了0,2,4,6 4个整数。
很显然console中的结果是0,2,4,6

第二个循环中,我们使用int16View以16位的格式读取同一块内存,可以想象原来的4个元素变成了8个元素,因此读出的时候也会将原来的32位元素拆分为2个16位元素。因此console中的结果是0,0,2,0,4,0,6,0

有同学可能对输出的顺序不理解,觉得为什么不是0,0,0,2,0,4,0,6呢。这里稍微解释一下。
原因是默认情况下typed array 读取数据的时候都是以小端模式来返回的。

所谓的小端模式(Little-endian),是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

上例在小端模式下的存储形式,每个框框表示16位。小端模式下数据的位权和地址的顺序是一致的,从小到大排列。

图片描述

总结

typed array提供了一组读取不同字长的类数组对象如,int8Array,int16Array,int64Array来实现对内存的操作。typed array的底层实现是基于ArrayBufferDataView实现的,对内存的分配和内存的读取做了很好的分层设计。使用typed array使js向C语言等具备了操作二进制数据的能力。


前端论道
414 声望11 粉丝