二进制数组(下)

前端开发
2018年10月23日
549

复合视图

由于视图的构造函数可以指定起始位置和长度,所以在同一段内存中可以依次存放不同类型的数据,这叫作“复合视图”。

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位整数。

如果一次读取两个或两个以上字节,,就必须明确数据的存储方式,到底是小端字节序还是大端字节序。默认情况下,DataViewget方法使用大端字节序解读数据,如果需要使用小端字节序解读,必须在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方法接受两个参数:第一个参数是字节序号,表示从哪个字节开始写入;第二个参数是写入的数据。对于那些写入两个或两个以上字节的方法,需要指定第三个参数,falseundefined表示使用大端字节序写入,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属性默认为textXHTMLHttpRequest第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标准入门》