类型化数组架构:缓冲(Buffer)和视图(View)
为了达到最大的灵巧性和效率,JavaScript类型化数组(TypedArray)将它的实现拆分为缓冲(Buffer)和视图(View)两部分;
缓冲(由ArrayBuffer工具实现,也称为缓冲器)描述的是一个数据块,是一个连续的内存区域;缓冲没有格式可言,并且不供应访问其内容的API;为了能访问缓冲工具中所包含的内存内容,须要利用视图,视图供应了高下文,即数据类型、起始偏移量和元素数,可以将缓冲数据转换为实际有类型的数组;

ArrayBuffer:ArrayBuffer是一种数据类型,用来表示通用的、固定长度的原始二进制数据缓冲区,常日在其他措辞中被称为“byte array”;
布局函数:ArrayBuffer(byteLength)参数byteLength指定了要创建的ArrayBuffer的大小,单位为字节;返回一个ArrayBuffer工具,其内容被初始化为0;
var buffer = new ArrayBuffer(8);console.log(buffer); // ArrayBuffer(8)
byteLength属性,是只读的,返回ArrayBuffer工具所包含的字节数;其在创建ArrayBuffer工具时就已经被指定了,不可改变;
console.log(buffer.byteLength); // 8
把稳,不要把ArrayBuffer工具与正常的数组稠浊;
ArrayBuffer和Array差异:
Array可以保存任何类型的值,但ArrayBuffer只能保存0和1组成的二进制数据;Array存放在堆中,而ArrayBuffer存储在栈中;ArrayBuffer构建后,大小是固定的,但数组则可以自由增减;Array供应一系列操作数组、元素的API,但ArrayBuffer没有;ArrayBuffer静态方法:isView(arg):如果参数arg是ArrayBuffer的视图实例则返回true,例如TypedArray工具或DataView工具;否则返回false;
var buffer = new ArrayBuffer(8);var int8Array = new Int8Array(buffer);console.log(ArrayBuffer.isView(int8Array)); // truetransfer(oldBuffer [, newByteLength]):
返回一个新的ArrayBuffer工具,其内容取自oldBuffer中的数据,并且根据newByteLength 的大小对数据进行截取或补0;
ArrayBuffer实例方法:slice(begin[, end]):返回一个新的ArrayBuffer;其基于源ArrayBuffer工具数据,从begin(包括)开始,到end(不包括)结束这段区域内容,拷贝并创建一个新ArrayBuffer并返回;参数:begin:从零开始的字节索引;end:可选,结束的字节索引,但不包含end;如果没指定end,新的ArrayBuffer将包含源ArrayBuffer从头到尾的所有字节;
var buffer = new ArrayBuffer(16);var slicedBuffer = buffer.slice(4,12); // 从4到11,不包括12console.log(slicedBuffer); // ArrayBuffer(8)console.log(buffer.byteLength); // 16console.log(slicedBuffer.byteLength); 8
如果begin或end是负数,则指的是从数组末端开始的索引,而不是从头开始;如果新ArrayBuffer的长度在打算后为负,它将逼迫为0;
不能直接操作ArrayBuffer的内容,而是要通过类型化数组视图TypedArray工具或一个描述缓冲数据格式的DataView工具来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读、写ArrayBuffer缓冲区的内容;
操作ArrayBuffer数据的视图,包括两种,一是TypedArray类型化数组视图,另一个是DataView数据视图;
TypedArray类型化数组视图:在读取或存储ArrayBuffer内存空间中的数据,可以利用不同的办法,这就叫做视图;一个类型化数组(TypedArray)描述了一个底层的二进制数据缓冲区的一个类数组工具,其供应了一种用于操作原始二进制数据的机制;其也是数组,是一个字节数组,其元素被设置为特定类型的值;
没有名为TypedArray的布局函数,它是多少个详细特定类型的布局函数、具有描述性的名字和所有常用的数值类型的视图组成的;类型 值范围 大小(bytes) 解释Int8Array -128 to 127 1 8位有符号整数Uint8Array 0 to 255 1 8位无符号整数Uint8ClampedArray 0 to 255 1 8位无符号整数Int16Array -32768 to 32767 2 16位有符号整数Uint16Array 0 to 65535 2 16位无符号整数Int32Array -2147483648 to 2147483647 4 32位有符号整数Uint32Array 0 to 4294967295 4 16位无符号整数Float32Array -3.4E38 to 3.4E38 4 32位IEEE浮点数(7位有效数字)Float64Array -1.8E308 to 1.8E308 8 64位IEEE浮点数(16位有效数字)BigInt64Array -2^63 to 2^63 - 1 8 64位有符号整数BigUint64Array 0 to 2^64 - 1 8 64位无符号整数
个中Uint8ClampedArray是一种分外类型的数组,它仅操作0到255之间的数值;
这些布局函数,在实例化时有多种形式,最大略的是传入一个length参数,表示包含的元素个数,如:
var int8 = new Int8Array(8);console.log(int8); // Int8Array(8)var int16 = new Int16Array(8);console.log(int16); Int16Array(8)
一旦创建了类型化数组,就可以像操作数组一样,操作类型化数组,例如利用方括号对类型化数组进行读写操作,如:
var int16 = new Int16Array(4);console.log(int16); // Int16Array(4),4个元素均为0int16[0] = 10;int16[1] = 20;int16[2] = 30;int16[3] = int16[0] + int16[1];console.log(int16[3]); // 30// 还可以利用for循环var int32 = new Int32Array(4);for(var i=0; i<int32.length; i++){int32[i] = i 2;}// 4个元素分别为0、2、4、6,每个元素4个字节,总大小是16console.log(int32);var bytes = new Uint16Array(1024); // 2KBfor(var i=0; i<bytes.length; i++){// bytes[i] = i;bytes[i] = i & 0xFF; // 设置为索引的低8位值 0xFF为255}console.log(bytes);
类型化数组虽然是数组,但是它和常规数组还是有一定的差异,如:类型化数组中的元素都是数字,利用布局函数在创建类型化数组时,就决定了数组中的数字(有符号或无符号或浮点数)的类型和大小;类型化数组视图有固定的长度,因此它的length属性为只读,并且短缺pop()、push()、shift()等能变动数组长度的方法;
在创建类型化数组视图的时候,数组中的元素总是默认初始化为0;每种类型化数组视图都以不同的办法表示数据,而同一数据视选择的类型的不同有可能会占用一或多个字节;例如8Byte的内存空间,可以保存8个Int8Array或Uint8Array,或者4个Int16Array或Uint16Array,或者2个Int32Array、Uint32Array或Float32Array,或者1个Float64Array;
var int8 = new Int8Array(8);var uint8 = new Uint8Array(8);var int16 = new Int16Array(4);var uint16 = new Uint16Array(4);var int32 = new Int32Array(2);var uint32 = new Uint32Array(2);var float32 = new Float32Array(2);var float64 = new Float64Array(1);
在为类型化数组视图工具元素赋值时,如果指定的字节数放不下相应的值,则实际保存的值是与最多表示的数的个数的模;例如,无符号16位整数所能表示得最多的数的个数是65536,如果你想保存65536,那实际保存的值是0,如果你想保存65537,那实际保存的值是1,以此类推;如:
var uint16 = new Uint16Array(10);uint16[0] = 65537;console.log(uint16[0]); // 1
数据类型不匹配时并不会抛出错误,以是必须要担保所赋的值不会超过相应元素的字节限定;
转换为普通数组:在处理完一个类型化数组后,有时须要把它转为普通数组,以便可以像普通数组一样去操作;可以调用Array.from实现转换,如果不支持Array.from的话,还可以:
var typedArray = new Uint8Array([1,2,3,4]),normalArray = Array.prototype.slice.call(typedArray);console.log(normalArray); // Array
Uint8ClampedArray类型:由于各个类型化数组的元素都会有规定的数值范围,因此超出该范围就会溢出;个中Uint8ClampedArray的溢出处理较为分外,它的数值范围在0到255之间,如果缓冲区所存的值超出该范围,那么就会更换这个值,例如小于0的数值被转换成0,而大于255的数值则被转换成255,如:
var int8 = new Int8Array(2);int8[0] = 256;console.log(int8[0]); // 0var clamped = new Uint8ClampedArray(2);clamped[0] = 256;console.log(clamped[0]); //2255
例如,网页Canvas元素输出的二进制像素数据,便是Uint8ClampedArray,如:
<canvas id="myCanvas" width="300" height="300"></canvas><script>var canvas = document.getElementById('myCanvas');var ctx = canvas.getContext('2d');ctx.beginPath();ctx.rect(20,20,200,100);ctx.closePath();ctx.strokeStyle = "#F00";ctx.fillStyle = "#CCC";ctx.stroke();ctx.fill();var imageData = ctx.getImageData(0,0, 200, 100);console.log(imageData); // imageData// imageData里面有个data属性,其是一个Uint8ClampedArray工具var uint8 = imageData.data;console.log(uint8);</script>
当处理与图形干系的数字,或者与数学干系的数字的时候,类型化数组也很有用:
var matrix = new Float64Array(3); // 一个3X3的矩阵var point3d = new Int16Array(3); // 3D空间中的一点var rgba = new Uint8Array(4); // 一个4字节的RGBA像素值var sudoku = new Uint8Array(81); // 一个9X9的数独板
所有的类型化数组都继续自TypedArray类,因而类型化数组可以利用相同的布局函数参数来实例化,形如:
new TypedArray(length);new TypedArray(typedArray);new TypedArray(object);new TypedArray(buffer [, byteOffset [, length]]);
第一种形式:当传入length参数时,一个内部的数组缓冲区会被创建在内存中,该缓存区的大小是传入的length乘以数组中每个元素所占用的字节数,每个元素的值都被初始化0,,如:
var int8 = new Int8Array(8);console.log(int8);var int16 = new Int16Array(10);console.log(int16);
创建类型化数组视图的同时,会自动创建得当容量的buffer缓冲区
第二种形式:typedArray:可以传入一个任意类型化数组视图typedArray工具作为参数,该typedArray工具数据会被复制到一个新的类型化数组中;
var int16Array = new Int16Array(20);var newInt16Array = new Int16Array(int16Array);console.log(newInt16Array); // Int16Array(20)
新的类型化数组视图将会和原数组视图具有相同的长度;typedArray中的每个值在被复制到新的数组视图之前,会被转化为相应类型的布局函数;
// ...var uint8Array = new Uint8Array(int16Array);console.log(uint8Array); // Uint8Array(20)
第三种形式:传入一个object作为参数时,此object必须实现length属性,且如果指定元素的话,下标必须是数值,不能是字符串,值也只能为数字,或者能隐式转换成数字;
var int16Array = new Int16Array({});console.log(int16Array); // byteLength: 0var int16Array = new Int16Array({0:0,3:true,5:"5",8:25,length:20});console.log(int16Array); // byteLength: 40
数组也是工具,以是也可以基于普通数组创建类型化数组,如:
var view = new Uint8Array([1,2,3,4,260]);console.log(view);
第四种形式:buffer[, byteOffset[, length]]:buffer表示一个ArrayBuffer工具,byteOffset是指作为出发点的字节偏移量,length参数指定了元素个数;如果后两者都未传入,那么全体buffer都会被读取,如果仅仅忽略length,那么会从byteOffset处读取到buffer的末端;用这种形式,就可以基于ArrayBuffer创建TypedArray工具,如:
var buffer = new ArrayBuffer(16); // 16个字节空间// 读取全体buffer// buffer: ArrayBuffer(16), byteLength: 16, byteOffset: 0, length: 16var int8 = new Int8Array(buffer);// 读取从第11个字节到末端的buffer// buffer: ArrayBuffer(16), byteLength: 6, byteOffset: 10, length: 6var int8 = new Int8Array(buffer, 10);// 读取从第5个字节,长度为8的元素个数// buffer: ArrayBuffer(16), byteLength: 8, byteOffset: 4, length: 8var int8 = new Int8Array(buffer, 4, 8);console.log(int8);
基于同一个ArrayBuffer,创建不同的类型化数组视图,如:
var buffer = new ArrayBuffer(16);// Int32Array(4) byteLength: 16, byteOffset: 0, length: 4var int32 = new Int32Array(buffer);// 创建一个Uint8Array视图,开始于字节2,直到缓冲区的末端// Uint8Array(14) byteLength: 14, byteOffset: 2, length: 14var uint8 = new Uint8Array(buffer, 2);// 创建一个Int16Array视图,开始于字节2,2个元素// Int16Array(2) byteLength: 4, byteOffset: 2, length: 2var int16 = new Int16Array(buffer, 2, 2);
以上的两个不同类型化数组都是同一数据的以不同格式展示出来的视图;除此之外,可以用以上所说的任何一种视图;基于同一个ArrayBuffer,创建的不同的类型化数组视图,利用了同一个数据缓冲区(ArrayBuffer),如:
console.log(int32.buffer === uint8.buffer); // trueconsole.log(uint8.buffer === int16.buffer); // trueconsole.log(int16.buffer === buffer); // true
只要任何一个视图对内存有所修正,就会在其它几个视图上反应出来,其余,由于这些视图的类型不同的,以是操作ArrayBuffer后,结果也是不同的,如:
// ...int16[0] = 32;console.log(int32[0]); // 2097152
两个相互重叠的视图所霸占的内存空间,操作个中的值以末了一次写入的为主(实在前面的操作已经解释了这个问题了,也便是后面的会覆盖前面的,再看看吧),如:
var buffer = new ArrayBuffer(4); // 4个字节var int8 = new Int8Array(buffer); // 从0到末端,4个元素var int16 = new Int16Array(buffer, 2); // 从2到末端,1个元素int8[0] = 1;int8[1] = 2;int8[2] = 3;int8[3] = 4;console.log(int8);console.log(int16);int16[0] = 500;console.log(int16);console.log(int8[2]); // -12console.log(int8[3]); // 1
复合视图:由于视图的布局函数可以指定起始位置和长度,以是在同一段内存之中,可以依次存放不同类型的数据,这叫做“复合视图”;如:
var buffer = new ArrayBuffer(24);var idView = new Uint32Array(buffer, 0, 1);var usernameView = new Uint8Array(buffer, 4, 16);var amountDueView = new Float32Array(buffer, 20, 1);
buffer属性:只读,返回该TypedArray(类型化数组视图工具)的内存区域对应的ArrayBuffer工具;
var buffer = new ArrayBuffer(16);var view = new Uint8Array(buffer);console.log(view.buffer === buffer); // true
创建类型化数组的同时,会创建一个数据缓冲区,即ArrayBuffer,由其buffer属性所引用;如:
var int8 = new Int8Array([1,2,3,4]);console.log(int8.buffer); // ArrayBuffer(4)
类型化数组中所有数据的读写,都是针对该buffer属性所引用的ArrayBuffer,如:
var int8 = new Int8Array([1,2,3,4]);console.log(int8[0]); // 1var buffer = int8.buffer;var t1 = new Int8Array(buffer);var t2 = new Int16Array(buffer);console.log(t1[0], t2[0]); // 1 513t1[0] = 20;console.log(int8[0]); // 20console.log(t2[0]); // 532
byteLength属性和byteOffset属性:byteLength属性返回类型化数组霸占的内存总长度,单位为字节;byteOffset属性返回类型化数组从底层ArrayBuffer工具的哪个字节开始;这两个属性都是只读属性;
类型化数据视图工具还具有一个常量BYTES_PER_ELEMENT,表示这种数据类型的元素所霸占的字节数,如:
var int8Array = new Int8Array(16);console.log(int8Array.BYTES_PER_ELEMENT); // 1console.log(new Float32Array(16).BYTES_PER_ELEMENT); // 4
可以利用这个属性来赞助初始化,如:
var buffer = new ArrayBuffer(30); // 30个字节console.log(buffer);var int8 = new Int8Array(buffer, 4, 10);console.log(int8); // Int8Array(10), byteLength: 10var uint16 = new Uint16Array(buffer, int8.byteOffset + int8.byteLength,(buffer.byteLength - int8.byteLength - int8.byteOffset) /Uint16Array.BYTES_PER_ELEMENT);console.log(uint16);
TypedArray类型化数组视图的方法:TypedArray视图工具拥有常规的Array工具的大部分方法,如:
var int16 = new Int16Array(20);for(var i=0; i<int16.length; i++){int16[i] = i;}console.log(int16);int16.forEach(function(v, i, arr){arr[i] = v 2;});console.log(int16);var a = int16.map(function(v, i, arr){return ++v;});console.log(a);console.log(int16.join());
类型化数组有固定的长度,因此它的length属性为只读,以是类似于pop()、push()、shift()等会变动数组长度的方法,不能利用,如:
int16.pop(); // int16.pop is not a function
除此这些,它还定义了一些用于设置和获取全体数组内容的方法;
set(array | typedarray [, offset])方法:用于复制数组,也便是将一段内容完备复制到另一段内存区域中;参数:array,拷贝数据的源数组,源数组的所有值都会被复制到目标数组中,除非源数组的长度加上偏移量超过目标数组的长度,而在这种情形下会抛出非常;typedarray,如果源数组是一个类型化数组,会将其的指向的内存区域内容拷贝到目标内存区域中;offset,可选,指定从自己的什么位置开始利用源数组的值;如果忽略该参数,则默认为0。
如:
// 从数组中拷贝var uint8 = new Uint8Array(20);var arr = new Array(0,1,2,3); // 一个4个字节的数组uint8.set(arr); // 将arr复制到开始uint8.set(arr, 4); // 在另一个偏移量处再次复制它们uint8.set([5,6,7,8], 8); // 或直接从一个常规数组中复制值console.log(uint8);uint8.set(arr,18);// 从TypedArray中拷贝var a = new Uint8Array(8); // 8个元素for(var i=0,len=a.length; i<len; i++){a[i] = i 2; // 给个值}var b = new Uint8Array(8); // 8个元素b.set(a);console.log(b); // 0,2,4...var c = new Uint16Array(10);c.set(a, 2);console.log(c); // 0,0,0,2,4,6,8...var d = new Uint16Array(10);// 抛出RangeError: offset is out of boundsd.set(a,4);
subarray([begin [,end]])方法:基于数组缓冲器的子集,创建一个新的视图,参数为开始元素的索引和结束元素的索引,返回的类型与源视图类型相同;参数:begin,可选,元素开始的索引,开始索引的元素将会被包括,若该值没有传入,将会返回一个拥有全部元素的数组;end,可选,元素结束的索引,结束索引的元素将不会被包括,若该值没有传入,从 begin 所指定的那一个元素到数组末端的所有元素都将会被包含进新数组中,如:
var uint8 = new Uint8Array(16);// 设点值for(var i=0; i<uint8.length; i++){uint8[i] = i;}console.log(uint8);var a = uint8.subarray();console.log(a); // byteLength: 16, byteOffset: 0, length: 16var b = uint8.subarray(2);console.log(b); // byteLength: 14, byteOffset: 2, length: 14var c = uint8.subarray(4,8);console.log(c); // byteLength: 4, byteOffset: 4, length: 4
subarray()方法不会创建数据的副本,它只是直接返回原数组的个中一部分内容,以是新数组视图和源数组视图共享同一个ArrayBuffer,以是,改动数组的内容将会影响到原数组,反之亦然,如:
// ...console.log(v1.buffer === v2.buffer); // truev2[0] = 18;console.log(v1[3]); // 18
ArrayBuffer与字符串的相互转换:
// ArrayBuffer转为字符串,参数为ArrayBuffer工具function uint162str(buffer) {// fromCharCode()方法可以返回由指定的UTF-16代码单元序列创建的字符串return String.fromCharCode.apply(null, new Uint16Array(buffer));}var uint16 = new Uint16Array([97,98,99,100]);console.log(uint162str(uint16)); // abcdvar uint8 = new Uint8Array([97,98,99,100]);console.log(uint162str(uint8)); // abcdvar str = "大师哥王唯";var codeArr = [];for(var i=0,len=str.length; i<len; i++){// charCodeAt() 方法返回一个数字,指示给定索引处的字符的Unicode值codeArr.push(str.charCodeAt(i));}console.log(codeArr);// 这样的Unicode值可以直接拿过来利用console.log(uint162str(codeArr)); // 大师哥王唯// 字符串转为ArrayBuffer或Unit16Array工具,参数为字符串function str2uint16(str) {var buffer = new ArrayBuffer(str.length2); // 每个字符占用2个字节var view = new Uint16Array(buffer);for (var i=0, strLen = str.length; i<strLen; i++) {view[i] = str.charCodeAt(i);}// return buffer; // 或return view;return view;}var str = "Web前端开拓";console.log(str2uint16(str));
XHR2中的ArrayBuffer:ArrayBuffer的运用特殊广泛,无论是WebSocket、WebAudio还是Ajax等,前端方面只假如处理大数据或者想提高数据处理性能,就会用到ArrayBuffer;在XHR2中,增加了responseType属性,用于设置相应的数据格式,可选的值有“text”、“arraybuffer”、“blob”、“document”和“json”,如:后端buffer.php:
<?php// echo 1234567; // 每个数字用一个字节,共7个字节// echo "大师哥王唯人"; // 每个中文用了3个字节,共18字节echo "wang大师哥18王唯"; // 中文用了3个字节,共21个字节
如:
function ab2str(buffer,callback){// 这里借用了我们立时要利用Blob工具和FileReader工具var blob = new Blob([buffer]);var reader = new FileReader();reader.readAsText(blob, 'UTF-8');reader.onload = function(event){if(callback){callback.call(null, reader.result);}}}function handler(result){console.log(result);}var xhr = new XMLHttpRequest();xhr.open('GET', 'buffer.php', true);xhr.onload = function(event){var buffer = this.response;ab2str(buffer, handler);};xhr.responseType = "arraybuffer";xhr.send(null);
或者利用TextDecoder工具,如:
xhr.onload = function(event){var buffer = this.response;var decoder = new TextDecoder("UTF-8");var uint8 = new Uint8Array(buffer);console.log(decoder.decode(uint8));};
Endianness(字节序):
端序又称字节序(Endianness),表示多字节中的字节排列办法;
大端字节序:高位字节在前,低位字节在后,也便是按照从高位到低位的顺序排列的,也称为高位优先(或简称为大端序);这是一种最符合人类阅读习气的办法;
小端字节序与大端字节序恰好相反,是指字节的最低有效位在最高有效位之前,也便是按照从低到高位的顺序排列,也称为低位优先(或简称为小端序);
例如:十进制数:16 909 060,对应的32位二进制数原码为:00000001 00000010 00000011 00000100
利用大端序表示,就即是它的原码;利用小端序表示,即:00000100 00000011 00000010 00000001
例如数字10,如果用16位二进制表示,为00000000 00001010(原始);如果用大端序存储的话,为00000000 00001010(从左往右存)如果用小端序存储的话,为00001010 00000000(从右往左存)如果用大端序读取的话,为00000000 00001010(从左往右读)如果用小端序读取的话,为00001010 00000000(从右往左存)
换算成16进制便是(0000 0000 0000 1010)000A;用大端序存储的话,该值表示为:000A;用小端序存储的话,该值表示为:0A00;
英特尔CPU处理器和多数浏览器采取的都是小端序(低位优先),这也是我们本地默认的办法;对付Int8Array和Uint8Array,由于它们是单字节的,以是涉及不到字节序的问题;
如果不愿定正在利用的打算机的字节序,可以采取下面的判断办法,如:
var littleEndian = (function() {var buffer = new ArrayBuffer(2);new DataView(buffer).setInt16(0, 256, true);return new Int16Array(buffer)[0] === 256;})();console.log(littleEndian); // true// 或者,用更大略的办法:var little_endian = new Int8Array(new Int32Array([1]).buffer)[0] === 1;console.log(little_endian); // true
虽然大多数CPU架构都采取小端序(低位优先),但是,很多的网络协议以及有些二进制文件格式,采取的是大端序(高位优先)的字节顺序;以是,当从文件中读取或从网络中下载字节时,就须要考虑平台的字节顺序;
常日,处理外部数据的时候,可以利用Int8Array和Uint8Array将数据视为一个单字节数组,不要利用其他的多字节字长的类型化数组;取而代之的是可以利用DataView类,这个类定义了采取显式指定的字节顺序从ArrayBuffer中读写其值的方法;
DataView数据视图:是一种底层接口,它供应有可以操作缓冲区中任意数据的读写方法;其是为理解决各种硬件设备、数据传输等对默认字节序的设定不一样而导致解码时会发生的混乱问题,其可以让开发者在对内存进行读写时,手动设定字节序的类型;
布局函数:DataView(buffer [, byteOffset [, byteLength]]):返回一个表示指天命据缓存区的DataView工具;参数:buffer:一个已经存在的ArrayBuffer或SharedArrayBuffer工具,即DataView工具的数据源;byteOffset:可选,buffer中的字节偏移量);如果未指定,则默认从第一个字节开始;byteLength:可选,字节长度;如果未指定,这个视图的长度将匹配buffer的长度,如:
var buffer = new ArrayBuffer(16);// 利用全体ArrayBuffervar view = new DataView(buffer);console.log(view); // DataView(16)// 从9开始的新视图var view1 = new DataView(buffer, 9);console.log(view1);// 从12开始,长度为4的字节的新视图var view2 = new DataView(buffer, 12, 4);// 设置索引12位置值为42,并打印出来// view2.setInt8(0, 42); // 42// view2.setInt16(0,42,true); // 42// view2.setInt16(0,42,false); // 0view2.setInt16(0,42); // 0console.log(view2.getInt8(0)); // 42 或 0
DataView实例属性:
buffer:只读,此视图引用的ArrayBuffer工具,即在构建时传入的buffer;byteLength:只读,此视图从原ArrayBuffer开始的长度(字节),即在构建时传入的ByteLength;byteOffset:只读,此视图从其ArrayBuffer开始的偏移量(字节),也是在构建时指定;// ...console.log(view1.byteOffset); // 0console.log(view1.byteLength); // 16console.log(view1.buffer === buffer); // true
DataView实例方法:读取或写入DataView的时候,要根据实现操作的数据类型,选择相应的getter和setter方法;getter方法:
getInt8(byteOffset):获取一个有符号8位整数(字节),该整数位于距视图开头的指定字节偏移量byteOffset处;getUint8(byteOffset):(读取1个字节),返回一个无符号的8位整数;getInt16(byteOffset, littleEndian):(读取2个字节),返回一个16位整数;getUint16(byteOffset, littleEndian):(读取2个字节),返回一个无符号的16位整数;getInt32(byteOffset, littleEndian):(读取4个字节),返回一个32位整数;getUint32(byteOffset, littleEndian):(读取4个字节),返回一个无符号的32位整数;getFloat32(byteOffset, littleEndian):(读取4个字节),返回一个32位浮点数;getFloat64(byteOffset, littleEndian):(读取8个字节),返回一个64位浮点数;getBigInt64(byteOffset, littleEndian):getBigUint64(byteOffset, littleEndian):以上一系列get方法的byteOffset参数都是一个字节偏移量,表示从哪个字节开始读取;
var buffer = new ArrayBuffer(16);var dataView = new DataView(buffer);console.log(dataView.getInt8(1)); // 0// 从第1个字节读取一个16位无符号整数var v1 = dataView.getUint16(1);// 从第4个字节读取一个16位无符号整数var v1 = dataView.getUint16(3);
如果一次读取两个或两个以上字节,就必须明确数据的存储办法:小端字节序还是大端字节序;默认情形下,DataView的get方法利用大端字节序解读数据,如果须要利用小端字节序解读,必须指定第二个参数为true,如:
// 小端字节序var v1 = dataView.getUint16(1, true);// 大端字节序var v2 = dataView.getUint16(3, false);// 大端字节序var v3 = dataView.getUint16(3);
setter方法:setInt8(byteOffset, value):在距视图开头的指定字节偏移byteOffset处,存储有符号8位整数值(字节);
setUInt8(byteOffset, value):写入1个字节的8位无符号整数;setInt16(byteOffset, value[, littleEndian]):setUInt16(byteOffset, value[, littleEndian]):setInt32(byteOffset, value[, littleEndian]):setUInt32(byteOffset, value[, littleEndian]):setFloat32(byteOffset, value[, littleEndian]):setFloat64(byteOffset, value[, littleEndian]):setBigInt64(byteOffset, value[, littleEndian]):setBigUint64(byteOffset, value[, littleEndian]):以上的setter方法,接管两个参数,第一个参数是字节序号,表示从哪个字节开始写入,第二个参数为写入的数据;
dataView.setInt8(1, 3);console.log(dataView.getInt8(1)); // 3
不同类型的数据,大小是不一样的,例如Uint8的整数须要1Byte,而Float32则要用4Byte;
var buffer = new ArrayBuffer(16); // 16Bytevar view = new DataView(buffer);// byteLength: 16, byteOffset: 0console.log(view);// 利用16位无符号整型设置索引为0的字节view.setUint16(0, 25);console.log(view.getInt8(0)); // 0console.log(view.getUint16(0)); // 25// 不能从字节1开始,由于16位整数要用2Byte// view.setUint16(1, 50); // 如果这样的话,打印getUint16(0)的结果就为0了// console.log(view.getInt8(0)); // 0// console.log(view.getUint16(0)); // 0view.setInt8(0, 18);view.setInt8(1, 28);console.log(view.getUint16(0)); // 4636
值得把稳的是,在DataView视图中,读写超出实在例化时的范围的值时,都会发生缺点,这跟的类型化数组视图不一样,以是在利用时须要更加谨慎,如:
view.setInt8(101,11); // 非常 RangeErrorview.getInt8(101); // 非常 RangeError
对付那些写入两个或两个以上字节的方法,须要指定第三个参数littleEndian,其是个布尔值,表示在读写数值时是否采取小端字节序而不是大端字节序(即将数据的最低有效位保存在高内存地址中),默认为false,即表示利用大端字节序写入,true表示利用小端字节序写入,如:
var buffer = new ArrayBuffer(16);var view = new DataView(buffer);// 在索引为0字节,以大端字节序写入值为25的32位整数view.setInt32(0, 25);// 证明一下,大端读取1个字节console.log(view.getInt8(3)); // 25// 32位有符号整数,大端读取,一次读4个字节console.log(view.getInt32(0)); // 25// 小端序(低位优先)读取4个字节// 读的顺序为:00011001 00000000 00000000 00000000console.log(view.getInt32(0, true)); // 419430400// 在小端序写入18的32位整数;// 小端序写入顺序:00011001 00000000 00000000 00000000view.setInt32(0, 18, true);// 大端序读取1个字节,读到的是00011001console.log(view.getInt8(0)); // 18// 大端序读取4个字节,读取的顺序为:00011001 00000000 00000000 00000000console.log(view.getInt32(0)); // 301989888// 在第5个字节,以默认(大端序)写入值为35的32位整数// 大端序与原码同等;view.setInt32(4, 35, false);// 以大端读,索引为7的字节,即00100011console.log(view.getInt8(7)); // 35// 以大端读,索引为4的字节,读4个字节,即:// 00000000 00000000 00000000 00100011console.log(view.getInt32(4)); // 35// 以小端读,索引为4的字节,读4个字节,即:// 00100011 00000000 00000000 00000000console.log(view.getInt32(4, true)); // 587202560// 在索引为8的字节,以小端字节序写入值为2.5的32位浮点数view.setFloat32(8, 2.5, true);// 以小端读,结果是:// 01000000 00100000 00000000 00000000console.log(view.getFloat32(8, true)); // 2.5// 以大端读索引为10和11的字节,即00100000和01000000console.log(view.getInt8(10),view.getInt8(11)); // 32 64// 以大端从索引为8开始读4个字节,结果是:// 00000000 00000000 00100000 01000000console.log(view.getInt32(8)); // 8256console.log(view.getFloat32(8)); // 1.156912012146569e-41
64位整数值:由于JavaScript目前不包含对64位整数值支持的标准,以是DataView不供应原生的64位操作;作为变通,可以实现一个getUint64()函数,以得到精度高达Number.MAX_SAFE_INTEGER的值,可以知足某些特定情形的需求,如:
function getUint64(dataview, byteOffset, littleEndian) {// 将 64 位整数值分成两份 32 位整数值var left = dataview.getUint32(byteOffset, littleEndian); // 前4个字节var right = dataview.getUint32(byteOffset+4, littleEndian); // 后4个字节var combined = littleEndian? left + 232right : 232left + right;// 判断一下,是否超过最大安全数的范围if (!Number.isSafeInteger(combined))console.warn(combined, '超过最大安全整数,精度可能会降落');return combined;}var buffer = new ArrayBuffer(8);var dataview = new DataView(buffer);dataview.setInt32(0,18);// 保存了一个32位整数,用了4个字节dataview.setInt32(4,25);// 在其后的索引为4的字节开始存一个32位整数,用了4个字节console.log(getUint64(dataview,0,false)); // 77309411353