在写做这篇博客的时候,参照了下面三篇博客:
http://www.javashuo.com/article/p-rbafwgjx-k.html (写的很详细,参照比较多)
http://www.javashuo.com/article/p-mheovhjb-bx.html
https://zh.javascript.info/arraybuffer-binary-arrays
文章中有一些内容是直接从上面博客复制过来的,并非想要抄袭,只是以为写博客能够增长理解度,别切能够避免遗忘。在此感谢上面三位博主的文章。
DataView部分彻底复制上面第一个连接的博客。javascript
ArrauBuffer对象、TypedArray视图和DataView视图是JavaScript中**专门操做二进制数据的接口**。他们都是以数组的方式操做二进制数组,因此被称为二进制数组。最初为了知足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通讯必须是二进制的,而不能是传统的文本格式的背景下诞生的。 一.ArrayBuffer相关介绍
ArrayBuffer指的是一段连续的内存区域。html
let buffer = new ArrayBuffer(40); // 在内存中开辟40个字节长度的内存区域 alert(buffer.byteLength); // 40
1.经过ArrayBuffer的构造函数能够开辟指定长度的内存区域,单位是字节。
2.在没有赋值的状况下,开辟的内存区域中都是以0填充的。
3.建立内存区域后,不管赋不赋值,内存区域的大小不会改变。
4.经过ArrayBuffer建立的内存区域,不能直接读写,须要经过视图来进行操做(在视图部分会进行讲解)。
5.开辟出来的内存区域是用来存放原始二进制数据的。java
当开辟的内存区域比较大的时候,可能会因为内存区域不足而报错,因此能够在建立完后看一看是否建立成功。数组
let buffer = new ArrayBuffer(40); if(buffer.byteLength === 40){ alert("建立成功"); }else{ alert("建立失败"); }
ArrayBuffer有一个长度属性,是byteLength,返回的是这个ArrayBuffer对象占多少个字节的内存大小。
还有一个比较经常使用的方法,是slice(begin,end)服务器
let buffer = new ArrayBuffer(40); let newBuffer = buffer.slice(10,30); alert(newBuffer.byteLength); // 20
slice经过拷贝原有对象的内存区域,生成一个新的内存区域。
由于不能直接对ArrayBuffer直接进行读写,因此可用的属性和方法也并很少。网络
上面也已经提到过,经过ArrayBuffer建立的内存区域并不能直接进行读写,而要经过TypedArray视图或者DataView视图进行读写操做,
他们的做用就是将内存区域中的数据以特定的格式表示出来,并经过这种特定的格式来操做这段内存区域。TypedArray视图和DataView视图的区别是,
TypedArray视图中的数组成员都是同一种类型,而DataView视图的数组成员能够是不一样的类型。app
二.TypedArray视图
TypedArray视图并无什么特别之处,它只是用来操做存放二进制数据内存的一种方式。
上面也已经提到过,做用就是讲内存区域以特定的格式表示出来,具体都有哪些特定的格式,JavaScript中有如下几种方式。ide
数据类型 | 字节长度 | 含义 |
---|---|---|
Int8Array | 1个字节 | 有符号整数,以8位(1个字节)的内存长度读写内存区域 |
Uint8Array | 1个字节 | 无符号整数,以8位(1个字节)的内存长度读写内存区域 |
Int16Array | 2个字节 | 有符号整数,以16位(2个字节)的内存长度读写内存区域 |
Unit16Array | 2个字节 | 无符号整数,以16位(2个字节)的内存长度读写内存区域 |
Int32Array | 4个字节 | 有符号整数,以32位(4个字节)的内存长度读写内存区域 |
Unit32Array | 4个字节 | 无符号整数,以32位(4个字节)的内存长度读写内存区域 |
Float32Array | 4个字节 | 浮点数,以32位(4个字节)的内存长度读写内存区域 |
Float64Array | 8个字节 | 浮点数,以64位(8个字节)的内存长度读写内存区域 |
以上8个数据类型,也是8个构造函数,都被称为TypedArray视图。函数
它们很像普通数组,都有length属性,都能用方括号运算符([])获取单个元素,全部数组的方法,在它们上面都能使用,因此TypedArray也被成为类型数组。ui
普通数组与 TypedArray 数组的差别主要在如下方面。
TypedArray 数组的全部成员,都是同一种类型。
TypedArray 数组的成员是连续的,不会有空位。
TypedArray 数组成员的默认值为 0。好比,new Array(10)返回一个普通数组,里面没有任何成员,只是 10 个空位;new Uint8Array(10)返回一个 TypedArray 数组,里面 10 个成员都是 0。
TypedArray 数组只是一层视图,自己不储存数据,它的数据都储存在底层的ArrayBuffer对象之中,要获取底层对象必须使用buffer属性。
咱们以Int16Array做为例子,来说解TypedArray视图的用法。
Int16Array中的16表示的单个元素所占内存的位数,也就是说若是用Int16Array构造函数建立了一个对象来读写二进制数据内存,这个数组对象每次的每一个元素都是16位(2个字节)大小。
能够经过BYTES_PER_ELEMENT属性,查看每一种类型中,元素所占内存大小。
alert(Int16Array.BYTES_PER_ELEMENT); // 2 每一个元素占2个字节内存大小
使用方法:
能够经过构造函数来建立TypedArray数组对象。
1.TypedArray(buffer, byteOffset=0, length)
第一个参数(必须):指定Arraybuffer对象,也就是说是经过指定的ArrayBuffer对象来建立TypedArray数组对象,前面也已经说过了,TypedArray视图自己并不存储数据,实际的数据仍是存在ArrayBuffer开辟的二进制内存区域上。
第二个参数:视图对象从ArrayBuffer内存区域的第几个字节开始,默认从0开始。
第三个参数:视图中包含的数据个数,默认是到指定ArrayBuffer对象内存区域的末尾。
// 开辟一段8个字节的内存区域 const buffer = new ArrayBuffer(8); // 在buffer内存区域上建立一个Unit32位视图,从字节0开始,一直到结尾 // 由于一个元素占32位(4个字节),总共有8个字节,因此在buffer这段内存区域上,能存储2个数据类型为Unit32的数据 let uint32 = new Uint32Array(buffer); // 在buffer内存区域上从第二个字节开始,建立4个数据类型为Unit8Array的数据 let uint8 = new Uint8Array(buffer,2,4); // 在buffer内存区域上从第四个字节开始,建立2个数据类型为Int16Array的数据 let int16 = new Int16Array(buffer,4,2);
上述代码执行结束后buffer内存示意图以下
从上面的代码以及示意图中能够看出,对于一段ArrayBuffer内存区域,能够同时建立多个不一样类型的TypedArray视图,可是这样会形成视图重叠,若是经过视图给内存赋值了,会形成数据覆盖。
2.TypedArray(length)
不经过ArrayBuffer,直接分配内存生成。其实这种方式的本质仍是会先建立一个Arraybuffer对象。
let uint32 = new Uint32Array(4); // 建立一个元素个数为4的Uint32类型视图对象 alert(uint32.buffer.byteLength); // 16 经过buffer属性,能够获取该视图锁对应的ArrayBuffer对象,能够看到该视图对象对应的ArrayBuffer内存区域大小为16个字节
3.TypedArray(typedArray)
经过另外一个TypedArray实例对象来建立一个TypedArray。
const typedArray = new Int8Array(new Uint8Array(4)); //0,0,0,0
上面代码中,Int8Array构造函数接受一个Uint8Array实例做为参数。
注意,此时生成的新数组,只是复制了参数数组的值,对应的底层内存是不同的。新数组会开辟一段新的内存储存数据,不会在原数组的内存之上创建视图。
const x = new Int8Array([1, 1]); const y = new Int8Array(x); x[0] // 1 y[0] // 1 x[0] = 2; y[0] // 1
上面代码中,数组y是以数组x为模板而生成的,当x变更的时候,y并无变更。
若是想基于同一段内存,构造不一样的视图,能够采用下面的写法。
const x = new Int8Array([1, 1]); const y = new Int8Array(x.buffer); x[0] // 1 y[0] // 1 x[0] = 2; y[0] // 2
4.TypedArray(arrayLikeObject)
构造函数的参数也能够是一个普通数组,而后直接生成TypedArray实例。const typedArray = new Uint8Array([1, 2, 3, 4]);
注意,这时TypedArray视图会从新开辟内存,不会在原数组的内存上创建视图。
上面代码从一个普通的数组,生成一个 8 位无符号整数的TypedArray实例。该数组有4个成员,每个都是8位无符号整数。
TypedArray 数组也能够转换回普通数组。const normalArray = [...typedArray];<br/>// or<br/>const normalArray = Array.from(typedArray);<br/>// or<br/>const normalArray = Array.prototype.slice.call(typedArray);
与普通数组相比,TypedArray 数组的最大优势就是能够直接操做内存,不须要数据类型转换,因此速度快得多。
ArrayBuffer 与字符串的互相转换
ArrayBuffer转为字符串,或者字符串转为ArrayBuffer,有一个前提,即字符串的编码方法是肯定的。假定字符串采用 UTF-16 编码(JavaScript 的内部编码方式),能够本身编写转换函数。
// ArrayBuffer 转为字符串,参数为 ArrayBuffer 对象 function ab2str(buf) { // 注意,若是是大型二进制数组,为了不溢出, // 必须一个一个字符地转 if (buf && buf.byteLength < 1024) { return String.fromCharCode.apply(null, new Uint16Array(buf)); } const bufView = new Uint16Array(buf); const len = bufView.length; const bstr = new Array(len); for (let i = 0; i < len; i++) { bstr[i] = String.fromCharCode.call(null, bufView[i]); } return bstr.join(''); } // 字符串转为 ArrayBuffer 对象,参数为字符串 function str2ab(str) { const buf = new ArrayBuffer(str.length * 2); // 每一个字符占用2个字节 const bufView = new Uint16Array(buf); for (let i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; }
三.DataView 视图
若是一段数据包括多种类型(好比服务器传来的 HTTP 数据),这时除了创建ArrayBuffer对象的复合视图之外,还能够经过DataView视图进行操做。
DataView视图提供更多操做选项,并且支持设定字节序。
原本,在设计目的上,ArrayBuffer对象的各类TypedArray视图,是用来向网卡、声卡之类的本机设备传送数据,因此使用本机的字节序就能够了;
而DataView视图的设计目的,是用来处理网络设备传来的数据,因此大端字节序或小端字节序是能够自行设定的。
DataView视图自己也是构造函数,接受一个ArrayBuffer对象做为参数,生成视图。
DataView(ArrayBuffer buffer [, 字节起始位置 [, 长度]]);
const buffer = new ArrayBuffer(24); const dv = new DataView(buffer);
DataView实例有如下属性,含义与TypedArray实例的同名方法相同。
DataView.prototype.buffer:返回对应的 ArrayBuffer 对象
DataView.prototype.byteLength:返回占据的内存字节长度
DataView.prototype.byteOffset:返回当前视图从对应的 ArrayBuffer 对象的哪一个字节开始
DataView 的读取
DataView实例提供 8 个方法读取内存。
getInt8:读取 1 个字节,返回一个 8 位整数。
getUint8:读取 1 个字节,返回一个无符号的 8 位整数。
getInt16:读取 2 个字节,返回一个 16 位整数。
getUint16:读取 2 个字节,返回一个无符号的 16 位整数。
getInt32:读取 4 个字节,返回一个 32 位整数。
getUint32:读取 4 个字节,返回一个无符号的 32 位整数。
getFloat32:读取 4 个字节,返回一个 32 位浮点数。
getFloat64:读取 8 个字节,返回一个 64 位浮点数。
这一系列get方法的参数都是一个字节序号(不能是负数,不然会报错),表示从哪一个字节开始读取。
// 从第一个字节开始读取8位无符号整数 const v1 = dv.getUint8(0); // 从第2个字节开始读取16位有符号整数,占2个字节 const v2 = dv.getInt16(1); // 从第4个字节开始读取16位有符号整数,2个字节 const v3 = dv.getInt16(3);
上面代码读取了ArrayBuffer对象的前 5 个字节,其中有一个 8 位整数和两个十六位整数。
若是一次读取两个或两个以上字节,就必须明确数据的存储方式,究竟是小端字节序仍是大端字节序。
默认状况下,DataView的get方法使用大端字节序解读数据,若是须要使用小端字节序解读,必须在get方法的第二个参数指定true。
// 小端字节序 const v1 = dv.getUint16(1, true); // 大端字节序 const v2 = dv.getUint16(3, false); // 大端字节序 const v3 = dv.getUint16(3);
DataView 的写入
DataView 视图提供 8 个方法写入内存。
setInt8:写入 1 个字节的 8 位整数。
setUint8:写入 1 个字节的 8 位无符号整数。
setInt16:写入 2 个字节的 16 位整数。
setUint16:写入 2 个字节的 16 位无符号整数。
setInt32:写入 4 个字节的 32 位整数。
setUint32:写入 4 个字节的 32 位无符号整数。
setFloat32:写入 4 个字节的 32 位浮点数。
setFloat64:写入 8 个字节的 64 位浮点数。
这一系列set方法,接受两个参数,
第一个参数是字节序号,表示从哪一个字节开始写入,第二个参数为写入的数据。
对于那些写入两个或两个以上字节的方法,须要指定第三个参数,false或者undefined表示使用大端字节序写入,true表示使用小端字节序写入。即默认大端字节序写入。
// 在第1个字节,以大端字节序写入值为25的32位整数 dv.setInt32(0, 25, false); // 在第5个字节,以大端字节序写入值为25的32位整数 dv.setInt32(4, 25); // 在第9个字节,以小端字节序写入值为2.5的32位浮点数 dv.setFloat32(8, 2.5, true);