遇见Javascript类型数组(Typed Array)

# Typed Arrays 

- Other

*Usage stats: Global
Support: 58.43%

JavaScript typed arrays provide a mechanism for accessing raw binary data much more efficiently.

Show all versions IE Firefox Chrome Safari Opera iOS Safari Opera Mini Android Browser Blackberry Browser
                2.1  
    12.0       3.2   2.2  
  7.0 13.0       4.0-4.1   2.3  
  8.0 14.0 20.0 5.1   4.2-4.3   3.0  
Current 9.0 15.0 21.0 6.0 12.0 5.0-5.1 5.0-7.0 4.0 7.0
Near future 10.0 16.0 22.0   12.5 6.0     10.0
Farther future   17.0 23.0            

 

Chrome的最新动态里提到了Typed Arrays(Typed Array,类型数组)这个概念,可能对很多人来说非常陌生,那么它是什么,又有什么用途呢?

之前的问题

        Web应用程序变得越来越强大,例如新增了音视频处理、WebSocket等多个功能特性。毫无疑问,如果Javascript能够快速方便的操作原始二进制数据会相当的有用。过去,我们必须要把原始数据当作字符串来处理,并且使用charCodeAt方法来从数据缓冲区中读取字节。

        但是这种方法需要多次转换数据(尤其在二进制数据不是字节格式的数据时,例如32位整数或者浮点数),所以非常慢而且容易出错。

        Javascript需要一种机制来更有效的访问原始的二进制数据,由此产生了类型数组。

定义

        其实除了Javascript,类型数组在其他很多语言中也有。它是一种数组,只有一种变量的类型。例如,一个float类型的数组将只包含浮点数而不能混用字符串和浮点数。此外,一个类型数组在初始化后不能改变大小。它看起来形式和普通Javascript数组很像,但是数据格式是一致和同一类型的(例如声音或者像素点的缓冲数据)。

        类型数组的规范参见这里。这个规范实质上定义了一种arrayBuffer类型,相当于一个普通的定长二进制缓冲区。我们不能直接访问和操作arrayBuffer的内容,而需要类型数组来创建arrayBuffer的视图(从技术上来说,类型数组等同于arrayBuffer,因为它们本质上是一样的)。例如,要访问32位有符号整数数组作为缓冲区,会创建一个Int32Array的类型数组来指向arrayBuffer。

        多个类型数组视图可以指向同一个arrayBuffer,采用不同的类型、不同的长度以及不同的位移。例如下面的代码:

 

[html]  view plain copy
  1. // 创建一个8字节的ArrayBuffer  
  2. var b = new ArrayBuffer(8);  
  3.   
  4. // 创建一个指向b的视图v1,采用Int32类型,开始于默认的字节索引0,直到缓冲区的末尾  
  5. var v1 = new Int32Array(b);  
  6.   
  7. // 创建一个指向b的视图v2,采用Uint8类型,开始于字节索引2,直到缓冲区的末尾  
  8. var v2 = new Uint8Array(b, 2);  
  9.   
  10. // 创建一个指向b的视图v3,采用Int16类型,开始于字节索引2,长度为2  
  11. var v3 = new Int16Array(b, 2, 2);  

 

        上述代码里变量的数据结构如下所示。

遇见Javascript类型数组(Typed Array)

变量的数据结构

        类型数组包括以下几种类型:

名称

大小 (以字节为单位)

说明

Int8Array

1

8位有符号整数

Uint8Array

1

8位无符号整数

Int16Array

2

16位有符号整数

Uint16Array

2

16位无符号整数

Int32Array

4

32位有符号整数

Uint32Array

4

32位无符号整数

Float32Array

4

32位浮点数

Float64Array

8

64位浮点数

 

        类型数组实际上目前是作为WebGL的一部分来实现的(和它相关的还有File API),但是它可以用在任何地方。

        下面我们来谈谈类型数组的优点和用途。

优点

1、  性能优秀

        所有类型数组相关的文档都提到的重要一点是,类型数组比传统数组快的多,具有非常好的性能。因为类型数组实际上是作为一个固定的内存块来进行访问的,而传统的普通Javascript数组使用的是Hash查找方式(因为元素长度不定)。

        这里有一个简单的测试结果,在Firefox4 Beta1版本,我们对比了一个普通数组和Float32Array数组在操作1亿个元素时每种操作所花费的时间。这个测试运行在Win7 64位、4G内存和Intel双核1.3G CPU的平台上。我们运行这个测试8次并使用其中最慢的一个时间。需要指出的是,普通Javascript数组的写入操作经常花费超过10秒钟,这会导致出现运行缓慢的脚本对话框。

操作

普通数组

Float32Array

8947

1455

1948

1109

循环复制

>10,000

1969

片段复制

1125

503

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

遇见Javascript类型数组(Typed Array)

 

性能优异的arrayBuffer

        其实在类型数组之前,Javascript也支持二进制字节的数组,这就是imageData。imageData是Canvas元素2D上下文环境里定义的数据类型。当在Canvas 2D里调用getImageData或者createImageData方法时就创建了imageData。imageData的data属性是一个字节数组,它实际大小是图片宽*高的四倍(因为每个像素有R、G、B、A四个通道)。之前我们在《用HTML5创建超酷图像灰度渐变效果》这篇文章里就用到了imageData。

        从图表数据来看,imageData和arrayBuffer在多个浏览器里创建的性能远远高于普通的数组(数据来源)。不过这里有一个问题,后面将会提到。

        可以想见,因为优秀的性能表现,类型数组可以广泛的应用于Javascript图像以及视频的处理和压缩,还有一些需要复杂运算的场景例如MD5计算中,让功能可以更快速更高效的完成。例如我们先把imageData转换为类型数组以换取更快的执行速度,如下面的代码:

[html] view plain copy
  1. var canvas = document.getElementById('canvas');  
  2. var canvasWidth  = canvas.width;  
  3. var canvasHeight = canvas.height;  
  4. var ctx = canvas.getContext('2d');  
  5. var imageData = ctx.getImageData(0,0, canvasWidth, canvasHeight);  
  6.    
  7. var buf =new ArrayBuffer(imageData.data.length);  
  8. var data =new Uint32Array(buf);  
  9.    
  10. for(var y =0; y < canvasHeight;++y){  
  11.     for(var x =0; x < canvasWidth;++x){  
  12.         var value = x * y &0xff;  
  13.    
  14.         data[y * canvasWidth + x]=  
  15.             (255   <<24)|    // alpha  
  16.             (value <<16)|    // blue  
  17.             (value <<  8)|    // green  
  18.              value;            // red  
  19.     }  
  20. }  

        另外一方面,因为类型数组可以显著增加HTML5 Canvas 2D Web App的性能,所以这一特性对于使用HTML5来创建Web游戏的开发者会非常重要

        下面是两个使用类型数组的示例。

        第一个是Energy2D的演示,用于对比普通数组和类型数组的性能,大家可以自行体验。

遇见Javascript类型数组(Typed Array)

 

Energy2D演示

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

遇见Javascript类型数组(Typed Array)

类型数组支持的在线SHA1计算器

 

2、  二进制支持

        上文曾经提到类型数组最主要的特点是支持二进制数据。的确,现在HTMl5的许多API涉及音视频和实时通信,这些功能经常依赖于二进制文件格式,例如MP3音频、MP4视频和PNG图像。二进制格式对于减少带宽,提高性能,以及与现有文件格式互相转换来说非常重要。

        类型数组使得Web应用可以使用多种二进制文件格式和直接操作文件的二进制内容,例如从现有的媒体文件中提取数据

        IE10上,已经提供了类型数组的支持(支持WebGL其实是微软非常纠结的事情)。我们可以看看微软所提供的二进制文件检测器的例子

        在这个示例里,我们可以获取音乐文件的ID3头,视频文件的原始字节数据,以及附加文件的格式。它的核心代码如下:

 

[html]  view plain copy
  1. function getHexChunk(buffer, startAt) {  
  2.     var chunkLength = Math.min(CHUNK_SIZE, buffer.byteLength - startAt)  
  3.     var uints = new Uint8Array(buffer, startAt, chunkLength);  
  4.     var rowString = "";  
  5.     for (var row = 0; row < uints.length; row += 16) {  
  6.         var remaining = uints.length - row;  
  7.         rowString += intToHexString(row + startAt, 8);  
  8.         rowString += "  ";  
  9.         for (var offset = 0; offset < 8 ; offset++) {  
  10.             if (offset < remaining) rowString += intToHexString(uints[row + offset], 2) + " ";  
  11.             else rowString += "  ";  
  12.         }  
  13.         rowString += " ";  
  14.         for (; offset < 16; offset++) {  
  15.             if (offset < remaining) rowString += intToHexString(uints[row + offset], 2) + " ";  
  16.             else rowString += "  ";  
  17.         }  
  18.         rowString += "  ";  
  19.         for (var offset = 0; offset < 8; offset++) {  
  20.             rowString += charForInt(uints[row + offset]);  
  21.         }  
  22.         for (; offset < 16; offset++) {  
  23.             rowString += charForInt(uints[row + offset]);  
  24.         }  
  25.         rowString += "\n";  
  26.     }  
  27.     return rowString;  
  28. }  

 

        页面上文件的二进制格式输出就是用这段代码实现的。

具体应用

        这里有一个使用类型数组在Canvas图像和二进制数据之间互相转换,然后通过WebSocket发送的示例。作者提到“在我实现二进制WebSocket示例时,我学习了很多Javascript类型数组的知识,了解了如何把对象转换为二进制数据。我写了一个示例来获取Canvas图像数据,并且把它通过二进制的WebSocket连接发送出去。WebSocket服务器获取图像数据,然后把它发送给所有连接的客户端(宇捷:这让我想起了最近国外非常火爆的超人气应用DrawSomething-你画我猜,我们可以用这种方式实现类似的WebApp),然后客户端再把Canvas数据还原为PNG图片。采用这种方式发送图像数据比起base64编码来更有效率(数据小33%,而且更利于序列化和存储)。”

遇见Javascript类型数组(Typed Array)

创造了历史的应用-你画我猜

        WebSocket支持二进制数据传输,对于WebSocket服务器来说,使用二进制数据会比UTF-8更为简单,不过现在浏览器支持方面还有问题。

        示例里实现将Canvas数据转换为二进制格式的代码如下:

 

[html]  view plain copy
  1. imagedata = context.getImageData(0, 0, imagewidth,imageheight);  
  2.   
  3. var canvaspixelarray = imagedata.data;  
  4.   
  5.   
  6. var canvaspixellen = canvaspixelarray.length;  
  7. var bytearray = new Uint8Array(canvaspixellen);  
  8.   
  9. for (var i=0;i<canvaspixellen;++i) {  
  10.      bytearray[i] = canvaspixelarray[i];  
  11. }  

 

        而把二进制数据还原为图像的代码如下,请注意我们不能直接从arrayBuffer获取数据直接放到Canvas中。

 

[html]  view plain copy
  1. var bytearray =new Uint8Array(event.data);  
  2.    
  3.    
  4. var tempcanvas = document.createElement('canvas');  
  5.     tempcanvas.heightimageheight;  
  6.     tempcanvas.widthimagewidth;  
  7. var tempcontext = tempcanvas.getContext('2d');  
  8.    
  9. var imgdata = tempcontext.getImageData(0,0,imagewidth,imageheight);  
  10.    
  11. var imgdatalen = imgdata.data.length;  
  12.    
  13. for(var i=8;i<imgdatalen;i++)  
  14. {  
  15.     imgdata.data[i]= bytearray[i];  
  16. }  
  17.   
  18. tempcontext.putImageData(imgdata,0,0);  

 

        在Adobe的官网上,也有一个类似的完整示例:《Real Time Data Exchange in HTML5 with WebSocket》。可以看到里面利用类型数组发送图片的代码如下:

 

[html]  view plain copy
  1. function sendphoto() {  
  2.   
  3.     imagedata = context.getImageData(0, 0, imagewidth,imageheight);  
  4.   
  5.     var canvaspixelarray = imagedata.data;  
  6.   
  7.       
  8.     var canvaspixellen = canvaspixelarray.length;  
  9.     var bytearray = new Uint8Array(canvaspixellen);  
  10.   
  11.     for (var i=0;i<canvaspixellen;++i) {  
  12.         bytearray[i] = canvaspixelarray[i];  
  13.     }  
  14.   
  15.     connection.send(bytearray.buffer);  
  16.     context.fillStyle = '#ffffff';  
  17.     context.fillRect(0, 0, imagewidth,imageheight);      
  18. }  

 

疑问

        理论上来看,类型数组的性能毫无疑问比普通数组更快,但是根据《现代浏览器里类型数组的性能》一文中的评测,可以看到某些操作和某些浏览器下,类型数组的性能反而更低,另外imageData和ArrayBuffer的性能在同一浏览器中还有不同的表现。这个现象让人困惑,因为imageData和ArrayBuffer其实就是为了性能敏感的功能诞生的,理论上能够提供更快的读取和写入速度。这有极大可能是目前浏览器厂商对于二进制数组优化不足造成的。我希望浏览器未来对于类型数组能有更好的支持。

遇见Javascript类型数组(Typed Array)

 

某些操作和浏览器下,类型数组性能反而更低

总结

        随着HTML5 Canvas、WebSocket等新特性的出现,WebApp能做的事情越来越多,同时Web App对于性能要求也越来越高,Javascript类型数组正是在这种情况下应运而生的。随着IE、Chrome、Opera等主流浏览器逐步提供对它的全面支持,以及可预期的性能优化,它将会发挥越来越重要的作用。

你可能感兴趣的:(JavaScript)