zoukankan      html  css  js  c++  java
  • 遇见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 versionsIEFirefoxChromeSafariOperaiOS SafariOpera MiniAndroid BrowserBlackberry 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,采用不同的类型、不同的长度以及不同的位移。例如下面的代码:

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

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

    变量的数据结构

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

    名称

    大小 (以字节为单位)

    说明

    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会快得多。

    性能优异的arrayBuffer

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

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

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

    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的演示,用于对比普通数组和类型数组的性能,大家可以自行体验。

     

    Energy2D演示

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

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

    2、  二进制支持

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

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

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

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

    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%,而且更利于序列化和存储)。”

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

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

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

    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中。

    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》。可以看到里面利用类型数组发送图片的代码如下:

    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其实就是为了性能敏感的功能诞生的,理论上能够提供更快的读取和写入速度。这有极大可能是目前浏览器厂商对于二进制数组优化不足造成的。我希望浏览器未来对于类型数组能有更好的支持。

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

    总结

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

  • 相关阅读:
    python视频教程大全(转载)
    数据挖掘十大经典算法(转载)
    等值线算法(转载)
    主成分分析PCA(转载)
    K-Means 算法(转载)
    面试常见问题小结
    二叉树的深度和宽度
    二叉树最大路径和-Binary Tree Maximum Path Sum
    C++中单例模式
    OC对象的动态和静态构造区别
  • 原文地址:https://www.cnblogs.com/litao229/p/2679824.html
Copyright © 2011-2022 走看看