json数组 javascript-第 71 节 类型化字段(ArrayBuffer 和 TypedArray 和 DataVi

Array中存储的对象可以动态增减,并且可以存储任意类型的值。 其底层实现相对复杂。 虽然Javascript引擎已经做了一些内部优化,以方便更快的现场操作,但是,随着Web应用程序的功能越来越强大,特别是一些新减少的功能,例如:canvas、WebGL、音频和视频编辑、访问原始WebSocket等数据。在这个应用中,如果使用传统字段,就会变得无能为力; ,可以使用新添加的TypedArray类型链表来操作这个原有的补码数据,这样会大大提升性能。

类型化字段架构:缓冲区和视图

为了实现最大的灵活性和效率,JavaScript 类型化字段(TypedArray)将其实现分为两部分:缓冲区(Buffer)和视图(View);

缓冲区(由ArrayBuffer对象实现,称为缓冲区)描述了一个数据块,它是一块连续的显示内存区域; 缓冲区根本没有格式,但不提供 API 来访问其内容; 为了能够访问缓冲对象中包含的数据显存的内容需要使用视图,视图提供了上下文,即数据类型、初始倾斜量和元素数量,并且可以将缓冲数据转换为实际类型字段;

数组缓冲区:

ArrayBuffer是一种数据类型,用于表示通用的、固定宽度的原始二进制补码数据缓冲区,在其他语言中一般称为“bytearray”;

构造函数: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的区别:

ArrayBuffer静态方式:

isView(arg):

如果参数arg是ArrayBuffer的视图实例,则返回true,例如TypedArray对象或DataView对象; 否则返回 false;

var buffer = new ArrayBuffer(8);
var int8Array = new Int8Array(buffer);
console.log(ArrayBuffer.isView(int8Array)); // true
transfer(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,不包括12
console.log(slicedBuffer); // ArrayBuffer(8)
console.log(buffer.byteLength); // 16
console.log(slicedBuffer.byteLength); 8

如果begin或end为正数,则指的是从链表末尾开始的索引,而不是从头开始; 如果估计后新的ArrayBuffer的厚度为负数,则强制为0;

ArrayBuffer 的内容不能直接操作,而必须通过类型化链表视图的 TypedArray 对象或描述缓冲数据格式的 DataView 对象来操作。 它们会将缓冲区中的数据表示为特定的格式,并通过这种格式读取。 ,写入ArrayBuffer缓冲区的内容;

操作ArrayBuffer数据有两种视图,一种是TypedArray类型链表视图,另一种是DataView数据视图;

TypedArray 类型链表视图:

可以使用不同的方法来读取或存储ArrayBuffer显存空间中的数据,这些数据称为视图;

类型化链表(TypedArray)描述了底层补码数据缓冲区的类字段对象,它提供了操作原始补码数据的机制;

它也是一个链表,一个字节链表,其元素被设置为特定类型的值;

没有名为 TypedArray 的构造函数,它由几个特定类型的构造函数、描述性名称以及所有常用数值类型的视图组成;

类型说明取值范围大小(字节)

Int8Array - 128 至 12718 位有符号整数

Uint8Array0 至 25518 位无符号整数

Uint8ClampedArray0 至 25518 位无符号整数

Int16Array - 32768 至 32767216 位有符号整数

Uint16Array0至65535216位无符号整数

Int32Array - 2147483648to2147483647432 位有符号整数

Uint32Array0to4294967295416 位无符号整数

Float32Array - 3.4E38to3.4E384 32 位 IEEE 浮点数(7 位有效数字)

Float64Array - 1.8E308to1.8E308864 位 IEEE 浮点数(16 位有效数字)

BigInt64Array - 2^63to2^63 - 1864 位有符号整数

BigUint64Array0to2^64 - 1864 位无符号整数

其中,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个元素均为0
int16[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个字节,总大小是16
console.log(int32);
var bytes = new Uint16Array(1024); // 2KB
for(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。如果要保存65537json数组 javascript,则实际保存的值为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]); // 0
var clamped = new Uint8ClampedArray(2);
clamped[0] = 256;
console.log(clamped[0]); //2255

例如网页Canvas元素输出的二补像素数据为Uint8ClampedArray,如:



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);

在处理与图形相关的数字或与物理相关的数字时,类型化链表也很有用:

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参数时,会在显存中创建一个内部链表缓冲区。 位元素的值全部初始化为0,如:

var int8 = new Int8Array(8);
console.log(int8);
var int16 = new Int16Array(10);
console.log(int16);

创建类型化链表视图时json数组 javascript,会手动创建一个合适容量的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)

第三种方式:当参数传入对象时,该对象必须实现length属性,并且如果指定元素,下标必须是值,不能是字符串,且值只能是数字,或者它可以隐式转换为数字;

var int16Array = new Int16Array({});
console.log(int16Array); // byteLength: 0
var 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参数指定元素数量; 如果后两者不传入,则读取整个缓冲区。 如果仅忽略 length,则将从 byteOffset 读取到缓冲区末尾;

通过这些方式,您可以基于ArrayBuffer创建TypedArray对象,例如:

var buffer = new ArrayBuffer(16); // 16个字节空间
// 读取整个buffer
// buffer: ArrayBuffer(16), byteLength: 16, byteOffset: 0, length: 16
var int8 = new Int8Array(buffer);
// 读取从第11个字节到末尾的buffer
// buffer: ArrayBuffer(16), byteLength: 6, byteOffset: 10, length: 6
var int8 = new Int8Array(buffer, 10);
// 读取从第5个字节,长度为8的元素个数
// buffer: ArrayBuffer(16), byteLength: 8, byteOffset: 4, length: 8
var int8 = new Int8Array(buffer, 4, 8);
console.log(int8);

基于同一个ArrayBuffer,创建不同类型的链表视图,如:

var buffer = new ArrayBuffer(16);
// Int32Array(4) byteLength: 16, byteOffset: 0, length: 4
var int32 = new Int32Array(buffer);
// 创建一个Uint8Array视图,开始于字节2,直到缓冲区的末尾
// Uint8Array(14) byteLength: 14, byteOffset: 2, length: 14
var uint8 = new Uint8Array(buffer, 2);
// 创建一个Int16Array视图,开始于字节2,2个元素
// Int16Array(2) byteLength: 4, byteOffset: 2, length: 2
var int16 = new Int16Array(buffer, 2, 2);

以上两种不同类型的链表是同一数据以不同格式显示的视图; 此外,可以使用上述任何视图;

基于同一个ArrayBuffer,创建的不同类型链表视图使用同一个数据缓冲区(ArrayBuffer),如:

console.log(int32.buffer === uint8.buffer); // true
console.log(uint8.buffer === int16.buffer); // true
console.log(int16.buffer === buffer); // true

只要任何视图改变了显存,就会反映在其他几个视图中。 另外,由于这个view的类型不同,操作ArrayBuffer后的结果也不同,比如:

// ...
int16[0] = 32;
console.log(int32[0]); // 2097152

两个重叠视图占用的显存空间,操作的值以最后写入的值为准(虽然后面的操作已经说明了这个问题,即前者会覆盖后者,我们看一下),like :

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]); // -12
console.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);

缓冲区属性:

只读,返回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)

typed字段中所有数据的读写都是针对buffer属性引用的ArrayBuffer,如:

var int8 = new Int8Array([1,2,3,4]);
console.log(int8[0]); // 1
var buffer = int8.buffer;
var t1 = new Int8Array(buffer);
var t2 = new Int16Array(buffer);
console.log(t1[0], t2[0]); // 1 513
t1[0] = 20;
console.log(int8[0]); // 20
console.log(t2[0]); // 532

byteLength 属性和 byteOffset 属性:

byteLength 属性返回类型字段占用的视频内存的总宽度,以字节为单位;

byteOffset 属性返回从底层 ArrayBuffer 对象的字节开始的类型化链表;

这两个属性都是只读属性;

类型化数据视图对象还有一个常量 BYTES_PER_ELEMENT,指示这些数据类型的元素抢占的字节数,例如:

var int8Array = new Int8Array(16);
console.log(int8Array.BYTES_PER_ELEMENT); // 1
console.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: 10
var 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 bounds
d.set(a,4);

子数组([开始[,结束]])方法:

根据字段缓冲区的子集创建一个新视图,参数为起始元素索引和结束元素索引,返回类型与源视图类型相同;

范围:

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: 16
var b = uint8.subarray(2);
console.log(b); // byteLength: 14, byteOffset: 2, length: 14
var c = uint8.subarray(4,8);
console.log(c); // byteLength: 4, byteOffset: 4, length: 4

subarray()方法不会创建数据的副本,它只是直接返回原始字段内容的一部分,因此新字段视图和源字段视图共享同一个ArrayBuffer,因此更改字段内容会影响原始字段,反之亦然,如:

// ...
console.log(v1.buffer === v2.buffer); // true
v2[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)); // abcd
var uint8 = new Uint8Array([97,98,99,100]);
console.log(uint162str(uint8)); // abcd
var 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.length*2); // 每个字符占用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,又称端序,表示多字节中的字节排列方式;

大端顺序:低位字节在前,高位字节在后,即按照从低位到高位的顺序排列,俗称低位在前(或简称为大位) - 字节序); 这是最适合人类阅读习惯的方法;

Little-Endian 字节顺序正好与 Big-Endian 字节顺序相反,表示字节的最低有效位在最高有效位之前,即按照从低位到低位的顺序排列,也称为作为高端(或简称为小端)序列);

例如:十补数:16909060,对应的32位补数的反码为:

00000001000000100000001100000100

使用大端表示法相当于其逆;

使用小端表示法,即:

00000100000000110000001000000001

例如数字10,如果用16位二进制补码表示,则为0000000000001010(原);

如果按照big endian顺序存储,则为0000000000001010(从左到右存储)

如果按照little endian顺序存储,则为0000101000000000(从右向左存储)

如果按照big endian顺序读取,则为0000000000001010(从左到右读取)

如果按照小端顺序读取,则为0000101000000000(从右向左存储)

转换成16的补码为(0000000000001010)000A;

如果以大端顺序存储,则该值表示为:000A;

如果按照小端顺序存储,则该值表示为:0A00;

Intel CPU处理器和大多数浏览器都使用little-endian顺序(高位在前),这也是我们本地默认的形式;

对于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架构都使用little-endian(高位优先)。 然而,许多网络合约和一些二补码文件格式使用大端(低端)字节顺序; 从网络读取或下载字节时,需要考虑平台的字节序;

一般在处理外部数据时,可以使用Int8Array和Uint8Array将数据视为单字节字段,而不要使用其他多字节字长的类型化字段;

相反,您可以使用 DataView 类,该类定义了以显式指定的字节顺序从 ArrayBuffer 读取和写入值的方式;

DataView数据视图:

它是一个低级套接字,它提供了一种在缓冲区中读写任意数据的方法; 是为了解决由于各种硬件设备和数据传输的默认字节顺序设置不同而在解码时出现的问题。 混乱问题,允许开发者在读写显存时自动设置字节顺序类型;

构造函数:

DataView(buffer[,byteOffset[,byteLength]]):返回代表指定数据缓冲区的DataView对象;

范围:

buffer:一个现有的ArrayBuffer或SharedArrayBuffer对象,它是DataView对象的数据源;

byteOffset:可选,缓冲区中的字节偏移); 如果不指定,默认从第一个字节开始;

byteLength:可选,字节宽度; 如果未指定,则该视图的厚度将与缓冲区的厚度相匹配,例如:

var buffer = new ArrayBuffer(16);
// 使用整个ArrayBuffer
var 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); // 0
view2.setInt16(0,42); // 0
console.log(view2.getInt8(0)); // 42 或 0

DataView实例属性:

// ...
console.log(view1.byteOffset); // 0
console.log(view1.byteLength); // 16
console.log(view1.buffer === buffer); // true

DataView实例方法:

在读取或写入DataView时,根据数据类型选择相应的getter和setter技术来实现操作;

获取方法:

上述一系列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);

如果一次读取两个或多个字节,则必须明确数据的存储格式:little-endian还是big-endian; 默认情况下,DataView的get方法使用big-endian来分析数据,如果需要使用little-endian字节序分析,则必须指定第二个参数为true,如:

// 小端字节序
var v1 = dataView.getUint16(1, true);
// 大端字节序
var v2 = dataView.getUint16(3, false);
// 大端字节序
var v3 = dataView.getUint16(3);

设置方法:

setInt8(byteOffset,value):在距视图开头指定的字节偏移byteOffset处存储有符号的8位整数值(byte);

上面的setter方法接受两个参数,第一个参数是字节序号,表示从哪个字节开始写入,第二个参数是写入的数据;

dataView.setInt8(1, 3);
console.log(dataView.getInt8(1)); // 3

不同类型的数据有不同的大小。 例如Uint8的整数需要1Byte,而Float32则需要4Byte;

var buffer = new ArrayBuffer(16); // 16Byte
var view = new DataView(buffer);
// byteLength: 16, byteOffset: 0
console.log(view);
// 使用16位无符号整型设置索引为0的字节
view.setUint16(0, 25);
console.log(view.getInt8(0)); // 0
console.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)); // 0
view.setInt8(0, 18);
view.setInt8(1, 28);
console.log(view.getUint16(0)); // 4636

值得注意的是,在DataView视图中,当读写超出其实例化范围的值时,就会出现错误。 这与类型化字段视图不同,因此使用时需要更加谨慎,例如:

view.setInt8(101,11); // 异常 RangeError
view.getInt8(101); // 异常 RangeError

对于这些写入两个或更多字节的方法,需要指定第三个参数littleEndian,它是一个布尔值,指示在读写值时是否使用little-endian而不是big-endian(官方的最低有效位)数据存放在显存高地址),默认为false,表示按big-endian字节顺序写入,true表示按little-endian字节顺序写入,如:

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 00000000
console.log(view.getInt32(0, true)); // 419430400
// 在小端序写入18的32位整数;
// 小端序写入顺序:00011001 00000000 00000000 00000000
view.setInt32(0, 18, true);
// 大端序读取1个字节,读到的是00011001
console.log(view.getInt8(0)); // 18
// 大端序读取4个字节,读取的顺序为:00011001 00000000 00000000 00000000
console.log(view.getInt32(0)); // 301989888
// 在第5个字节,以默认(大端序)写入值为35的32位整数
// 大端序与原码一致;
view.setInt32(4, 35, false);
// 以大端读,索引为7的字节,即00100011
console.log(view.getInt8(7)); // 35
// 以大端读,索引为4的字节,读4个字节,即:
// 00000000 00000000 00000000 00100011
console.log(view.getInt32(4)); // 35
// 以小端读,索引为4的字节,读4个字节,即:
// 00100011 00000000 00000000 00000000
console.log(view.getInt32(4, true)); // 587202560
// 在索引为8的字节,以小端字节序写入值为2.5的32位浮点数
view.setFloat32(8, 2.5, true);
// 以小端读,结果是:
// 01000000 00100000 00000000 00000000
console.log(view.getFloat32(8, true)); // 2.5
// 以大端读索引为10和11的字节,即00100000和01000000
console.log(view.getInt8(10),view.getInt8(11)); // 32 64
// 以大端从索引为8开始读4个字节,结果是:
// 00000000 00000000 00100000 01000000
console.log(view.getInt32(8)); // 8256
console.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 + 2**32*right : 2**32*left + 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