下面我们有一个关于普通数组、类型数组(arrayBuffer)以及imageData之间性能的比较,可以看到arrayBuffer会快得多。

性能优异的arrayBuffer
其实在类型数组之前,Javascript也支持二进制字节的数组,这就是imageData。imageData是Canvas元素2D上下文环境里定义的数据类型。当在Canvas 2D里调用getImageData或者createImageData方法时就创建了imageData。imageData的data属性是一个字节数组,它实际大小是图片宽*高的四倍(因为每个像素有R、G、B、A四个通道)。之前我们在《用HTML5创建超酷图像灰度渐变效果http://www.2cto.com/kf/201204/126014.html 》这篇文章里就用到了imageData。
从图表数据来看,imageData和arrayBuffer在多个浏览器里创建的性能远远高于普通的数组(数据来源)。不过这里有一个问题,后面将会提到。
可以想见,因为优秀的性能表现,类型数组可以广泛的应用于Javascript图像以及视频的处理和压缩,还有一些需要复杂运算的场景例如MD5计算中,让功能可以更快速更高效的完成。例如我们先把imageData转换为类型数组以换取更快的执行速度,如下面的代码:
[html] view plaincopyprint?var canvas = document.getElementById('canvas'); var canvascanvasWidth = canvas.width; var canvascanvasHeight = canvas.height; var ctx = canvas.getContext('2d'); var imageData = ctx.getImageData(0,0, canvasWidth, canvasHeight); var buf =new ArrayBuffer(imageData.data.length); var data =new Uint32Array(buf); for(var y =0; y < canvasHeight;++y){ for(var x =0; x < canvasWidth;++x){ var value = x * y &0xff; data[y * canvasWidth + x]= (255 <<24)| // alpha (value <<16)| // blue (value << 8)| // green value; // red } } var canvas = document.getElementById('canvas');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0,0, canvasWidth, canvasHeight);
var buf =new ArrayBuffer(imageData.data.length);
var data =new Uint32Array(buf);
for(var y =0; y < canvasHeight;++y){
for(var x =0; x < canvasWidth;++x){
var value = x * y &0xff;
data[y * canvasWidth + x]=
(255 <<24)| // alpha
(value <<16)| // blue
(value << 8)| // green
value; // red
}
} 另外一方面,因为类型数组可以显著增加HTML5 Canvas 2D Web App的性能,所以这一特性对于使用HTML5来创建Web游戏的开发者会非常重要。
下面是两个使用类型数组的示例。
第一个是Energy2D的演示,用于对比普通数组和类型数组的性能,大家可以自行体验。
验。

Energy2D演示
第二个示例是使用类型数组、FileAPI以及Web Workers实现的SHA1在线计算器,它的性能相当出色。正是在类型数组的支持下,Javascript执行SHA1、MD5这样复杂运算的速度变得越来越快。

类型数组支持的在线SHA1计算器
2、 二进制支持
上文曾经提到类型数组最主要的特点是支持二进制数据。的确,现在HTMl5的许多API涉及音视频和实时通信,这些功能经常依赖于二进制文件格式,例如MP3音频、MP4视频和PNG图像。二进制格式对于减少带宽,提高性能,以及与现有文件格式互相转换来说非常重要。
类型数组使得Web应用可以使用多种二进制文件格式和直接操作文件的二进制内容,例如从现有的媒体文件中提取数据。
在IE10上,已经提供了类型数组的支持(支持WebGL其实是微软非常纠结的事情)。我们可以看看微软所提供的二进制文件检测器的例子:

在这个示例里,我们可以获取音乐文件的ID3头,视频文件的原始字节数据,以及附加文件的格式。它的核心代码如下:
[html] view plaincopyprint?function getHexChunk(buffer, startAt) {
var chunkLength = Math.min(CHUNK_SIZE, buffer.byteLength - startAt)
var uints = new Uint8Array(buffer, startAt, chunkLength);
var rowString = "";
for (var row = 0; row < uints.length; row += 16) {
var remaining = uints.length - row;
rowString += intToHexString(row + startAt, 8);
rowString += " ";
for (var offset = 0; offset < 8 ; offset++) {
if (offset < remaining) rowString += intToHexString(uints[row + offset], 2) + " ";
else rowString += " ";
}
rowString += " ";
for (; offset < 16; offset++) {
if (offset < remaining) rowString += intToHexString(uints[row + offset], 2) + " ";
else rowString += " ";
}
rowString += " ";
for (var offset = 0; offset < 8; offset++) {
rowString += charForInt(uints[row + offset]);
}
for (; offset < 16; offset++) {
rowString += charForInt(uints[row + offset]);
}
rowString += "/n";
}
return rowString;
}
function getHexChunk(buffer, startAt) {
var chunkLength = Math.min(CHUNK_SIZE, buffer.byteLength - startAt)
var uints = new Uint8Array(buffer, startAt, chunkLength);
var rowString = "";
for (var row = 0; row < uints.length; row += 16) {
var remaining = uints.length - row;
rowString += intToHexString(row + startAt, 8);
rowString += " ";
for (var offset = 0; offset < 8 ; offset++) {
if (offset < remaining) rowString += intToHexString(uints[row + offset], 2) + " ";
else rowString += " ";
}
rowString += " ";
for (; offset < 16; offset++) {
if (offset < remaining) rowString += intToHexString(uints[row + offset], 2) + " ";
else rowString += " ";
}
rowString += " ";
for (var offset = 0; offset < 8; offset++) {
rowString += charForInt(uints[row + offset]);
}
for (; offset < 16; offset++) {
rowString += charForInt(uints[row + offset]);
}
rowString += "/n";
}
return rowString;
}
页面上文件的二进制格式输出就是用这段代码实现的。
具体应用
这里http://blog.digitalbackcountry.com/2012/01/dealing-with-binary-data-from-a-canvas-object-using-javascript-typedarrays/ 有一个使用类型数组在Canvas图像和二进制数据之间互相转换,然后通过WebSocket发送的示例。作者提到“在我实现二进制WebSocket示例时,我学习了很多Javascript类型数组的知识,了解了如何把对象转换为二进制数据。我写了一个示例来获取Canvas图像数据,并且把它通过二进制的WebSocket连接发送出去。WebSocket服务器获取图像数据,然后把它发送给所有连接的客户端(宇捷:这让我想起了最近国外非常火爆的超人气应用DrawSomething-你画我猜,我们可以用这种方式实现类似的WebApp),然后客户端再把Canvas数据还原为PNG图片。采用这种方式发送图像数据比起base64编码来更有效率(数据小33%,而且更利于序列化和存储)。”

创造了历史的应用-你画我猜
WebSocket支持二进制数据传输,对于WebSocket服务器来说,使用二进制数据会比UTF-8更为简单,不过现在浏览器支持方面还有问题。
示例里实现将Canvas数据转换为二进制格式的代码如下:
[html] view plaincopyprint?imagedata = context.getImageData(0, 0, imagewidth,imageheight);
var canvaspixelarray = imagedata.data;
var canvaspixellen = canvaspixelarray.length;
var bytearray = new Uint8Array(canvaspixellen);
for (var i=0;i<canvaspixellen;++i) {
bytearray[i] = canvaspixelarray[i];
}
imagedata = context.getImageData(0, 0, imagewidth,imageheight);
var canvaspixelarray = imagedata.data;
var canvaspixellen = canvaspixelarray.length;
var bytearray = new Uint8Array(canvaspixellen);
for (var i=0;i<canvaspixellen;++i) {
bytearray[i] = canvaspixelarray[i];
}
而把二进制数据还原为图像的代码如下,请注意我们不能直接从arrayBuffer获取数据直接放到Canvas中。
[html] view plaincopyprint?var bytearray =new Uint8Array(event.data);
var tempcanvas = document.createElement('canvas');
tempcanvas.height= imageheight;
tempcanvas.width= imagewidth;
var tempcontext = tempcanvas.getContext('2d');
var imgdata = tempcontext.getImageData(0,0,imagewidth,imageheight);
var imgdataimgdatalen = imgdata.data.length;
for(var i=8;i<imgdatalen;i++)
{
imgdata.data[i]= bytearray[i];
}
tempcontext.putImageData(imgdata,0,0);
var bytearray =new Uint8Array(event.data);
var tempcanvas = document.createElement('canvas');
tempcanvas.height= imageheight;
tempcanvas.width= imagewidth;
var tempcontext = tempcanvas.getContext('2d');
var imgdata = tempcontext.getImageData(0,0,imagewidth,imageheight);
var imgdatalen = imgdata.data.length;
for(var i=8;i<imgdatalen;i++)
{
imgdata.data[i]= bytearray[i];
}
tempcontext.putImageData(imgdata,0,0);
在Adobe的官网上,也有一个类似的完整示例http://www.adobe.com/devnet/html5/articles/real-time-data-exchange-in-html5-with-websockets.html :《Real Time Data Exchange in HTML5 with WebSocket》。可以看到里面利用类型数组发送图片的代码如下:
[html] view plaincopyprint?function sendphoto() {
imagedata = context.getImageData(0, 0, imagewidth,imageheight);
var canvaspixelarray = imagedata.data;
var canvaspixellen = canvaspixelarray.length;
var bytearray = new Uint8Array(canvaspixellen);
for (var i=0;i<canvaspixellen;++i) {
bytearray[i] = canvaspixelarray[i];
}
connection.send(bytearray.buffer);
context.fillStyle = '#ffffff';
context.fillRect(0, 0, imagewidth,imageheight);
}
function sendphoto() {
imagedata = context.getImageData(0, 0, imagewidth,imageheight);
var canvaspixelarray = imagedata.data;
var canvaspixellen = canvaspixelarray.length;
var bytearray = new Uint8Array(canvaspixellen);
for (var i=0;i<canvaspixellen;++i) {
bytearray[i] = canvaspixelarray[i];
}
connection.send(bytearray.buffer);
context.fillStyle = '#ffffff';
context.fillRect(0, 0, imagewidth,imageheight);
}
疑问
理论上来看,类型数组的性能毫无疑问比普通数组更快,但是根据《现代浏览器里类型数组的性能http://blog.n01se.net/?p=248 》一文中的评测,可以看到某些操作和某些浏览器下,类型数组的性能反而更低,另外imageData和ArrayBuffer的性能在同一浏览器中还有不同的表现。这个现象让人困惑,因为imageData和ArrayBuffer其实就是为了性能敏感的功能诞生的,理论上能够提供更快的读取和写入速度。这有极大可能是目前浏览器厂商对于二进制数组优化不足造成的。我希望浏览器未来对于类型数组能有更好的支持。

某些操作和浏览器下,类型数组性能反而更低
总结
随着HTML5 Canvas、WebSocket等新特性的出现,WebApp能做的事情越来越多,同时Web App对于性能要求也越来越高,Javascript类型数组正是在这种情况下应运而生的。随着IE、Chrome、Opera等主流浏览器逐步提供对它的全面支持,以及可预期的性能优化,它将会发挥越来越重要的作用。
附:类型数组的浏览器支持情况http://caniuse.com/#feat=typedarrays
摘自 蒋宇捷的博客