二进制数组(下)
复合视图
由于视图的构造函数可以指定起始位置和长度,所以在同一段内存中可以依次存放不同类型的数据,这叫作“复合视图”。
js
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);
上面的代码将一个24字节的ArrayBuffer对象
分成了3个部分:
- 字节0到字节3:1个32位无符号整数。
- 字节4到字节19:16个8位整数。
- 字节20到字节23:1个32位浮点数。
这一数据结构可以用C语言
描述如下:
c
struct someStruct {
unsigned long id;
char username[16];
float amountDue;
}
DataView视图
如果一段数据包括多种类型(比如服务器传来的HTTP数据),这时除了建立ArrayBuffer对象
的复合视图外,还可以通过DataView视图
进行操作。
DataView视图
提供更多操作选项,而且支持设定字节序。本来,在设计目的上,ArrayBuffer对象
的各种TypedArray视图
用于向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序即可;而DataView视图
用于处理网络设备传来的数据,所以大端字节序
或小端字节序
可以自行设定。
DataView
本身也是一个构造函数,接受一个ArrayBuffer对象
作为参数生成视图。
js
DataView(ArrayBuffer buffer[, 字节起始位置[, 长度]]);
下面是一个例子:
js
var buffer = new ArrayBuffer(24);
var dv = new DataView(buffer);
DataView实例
有以下属性,含义与TypedArray实例
的同名方法相同。
DataView.prototype.buffer
:返回对应的ArrayBuffer对象
。DataView.prototype.byteLength
:返回占据的内存字节长度。DataView.prototype.byteOffset
:返回当前视图从对应的ArrayBuffer对象
的哪个字节开始。
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方法
的参数都是一个字节序号(不能是负数,否则会报错),表示从哪个字节开始读取。
js
var buffer = new ArrayBuffer(24);
var dv = new DataView(buffer);
// 从第1个字节读取一个8位无符号整数
var v1 = dv.getUint8(0);
// 从第2个字节读取一个16位无符号整数
var v2 = dv.getUint16(1);
// 从第4个字节读取一个16位无符号整数
var v3 = dv.getUint16(3);
上面的代码读取了ArrayBuffer对象
的前5个字节,其中有1个8位整数和2个16位整数。
如果一次读取两个或两个以上字节,,就必须明确数据的存储方式,到底是小端字节序
还是大端字节序
。默认情况下,DataView
的get方法
使用大端字节序
解读数据,如果需要使用小端字节序
解读,必须在get方法
的第二个参数指定true
。
js
// 小端字节序
var v1 = dv.getUint16(1, true);
// 大端字节序
var v2 = dv.getUint16(3, false);
var v3 = dv.getUint16(3);
DataView视图
提供8个方法写入内存:
setInt8
setUint8
setInt16
setUint16
setInt32
setUint32
setFloat32
setFloat64
这一系列set方法
接受两个参数:第一个参数是字节序号,表示从哪个字节开始写入;第二个参数是写入的数据。对于那些写入两个或两个以上字节的方法,需要指定第三个参数,false
或undefined
表示使用大端字节序
写入,true
表示使用小端字节序
写入。
js
// 在第 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)
如果不确定所使用计算机的字节序,可以采用下面的判断方式:
js
var littleEndian = (function () {
var buffer = new ArrayBuffer(2);
new DataView(buffer).setInt16(0, 256, true);
return new Int16Array(buffer)[0] === 256;
})
如果返回true
,就是小端字节序
;如果返回false
,就是大端字节序
。
二进制数组的应用
大量的Web API
用到了ArrayBuffer对象
及其视图对象
。
AJAX
传统上,服务器通过AJAX
操作只能返回文本数据,即responseType属性
默认为text
。XHTMLHttpRequest
第2版——XHR2
允许服务器返回二进制数据类型,可以把返回类型(responseType
)设置为arraybuffer
;如果不知道,就设置为blob
。
js
var xhr = new XMLHttpRequest();
xhr.open('GET', someUrl);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
let arrayBuffer = xhr.response;
// ...
}
xhr.send();
如果知道传回来的是32位整数,可以像下面这样处理:
js
xhr.onreadstatechange = function () {
if (req.readyState === 4) {
var arrayResponse = xhr.response;
var dataView = new DataView(arrayResponse);
var ints = new Uint32Array(dataView.byteLength / 4);
xhrDiv.style.backgroundColor = '#00ff00';
xhrDiv.innerText = 'Array is ' + ints.length + 'uints long';
}
}
Canvas
网页Canvas元素
输出的二进制像素数据就是TypedArray数组
。
js
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, canvas.width, canvas,height);
var uint8ClampedArray = imageData.data;
需要注意的是,上面的typedArray
虽然是一个TypedArray数组
,但它的视图类型是一种针对Canvas元素
的专有类型——Uint8ClampedArray
。这个视图类型的特点就是专门针对颜色,把每个字节解读为无符号的8位整数,即只能取值0 ~ 255
,而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。
举例来说,如果把像素的颜色值设置为Uint8Array类型
,那么乘以一个gamma值
的时候,就必须这样计算:
js
u8[i] = Math.min(255, Math.max(0, u8[i] * gamma));
因为Uint8Array类型
对于大于255的运算结果(比如0xFF+1)会自动变为0x00
,所以图像处理必须要像上面这样算。这样做很麻烦,而且影响性能。如果将颜色值设置为Uint8ClampedArray类型
,计算就将简化许多。
js
pixels[i] *= gamma;
Uint8ClampedArray类型
确保将小于0的值设置为0,将大于255的值设置为255。注意,IE10不支持该类型。
WebSocket
WebSocket
可以通过ArrayBuffer
发送或接收二进制数据。
js
var socket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';
// Wait until socket is open
socket.addEventListener('open', function (event) {
// Send binary data
var typeArray = new Uint8Array(4);
socket.send(typeArray.buffer);
});
// Receive binary data
socket.addEventListener('message', function (event) {
var arrayBuffer = event.data;
// ...
});
File API
如果知道一个文件的二进制类型,也可以将这个文件读取为ArrayBuffer对象
。
js
var fileInput = document.getElementById('fileInput');
var file = fileInput.files[0];
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
var arrayBuffer = reader.result;
// ...
};
下面以处理BMP文件
为例。假定file变量
是一个指向BMP文件
的文件对象,首先读取文件。
js
var reader = new FileReader();
reader.addEventListener('load', processimage, false);
reader.readAsArrayBuffer(file);
然后,定义处理函数的回调函数:先在二进制数据之上建立一个DataView视图
,再建立一个bitmap对象
,用于存放处理后的数据,最后将图像展示在canvas元素
中。
js
function processimage (e) {
var buffer = e.target.result;
var datav = new DataView(buffer);
var bitmap = {};
// ... 具体处理步骤
}
具体处理图像数据时,先处理BMP
的文件头,具体每个文件头的格式和定义,请参阅相关资料。
js
bitmap.fileheader = {};
bitmap.fileheader.bfType = datav.getUint16(0, true);
bitmap.fileheader.bfSize = datav.getUint32(2, true);
bitmap.fileheader.bfReserved1 = datav.getUint16(6, true);
bitmap.fileheader.bfReserved2 = datav.getUint16(8, true);
bitmap.fileheader.bfOffBits = datav.getUint32(10, true);
接着处理图像元信息部分。
js
bitmap.infoheader = {};
bitmap.infoheader.biSize = datav.getUint32(14, true);
bitmap.infoheader.biWidth = datav.getUint32(18, true);
bitmap.infoheader.biHeight = datav.getUint32(22, true);
bitmap.infoheader.biPlanes = datav.getUint16(26, true);
bitmap.infoheader.biBitCount = datav.getUint16(28, true);
bitmap.infoheader.biCompression = datav.getUint32(30, true);
bitmap.infoheader.biSizeImage = datav.getUint32(34, true);
bitmap.infoheader.biXPelsPerMeter = datav.getUint32(38, true);
bitmap.infoheader.biYPelsPerMeter = datav.getUint32(42, true);
bitmap.infoheader.biClrUsed = dastav.getUint32(46, true);
bitmap.infoheader.biClrImportant = datav.getUint32(50, true);
最后处理图像本身的像素信息。
js
var start = bitmap.fileheader.bf0ffBits;
bitmap.pixels = new Uint8Array(buffer, start);
至此,图像文件的数据全部处理完成。下一步,可以根据需要进行图像变形,或者转换格式,或者展示在Canvas网页元素
中。
以上,摘抄自阮一峰老师的《ES6标准入门》