我在Chrome的[最新动态](http://blog.csdn.net/hfahe/article/details/7408426)里提到了Typed Arrays(Typed Array,类型数组)这个概念,可能对很多人来说非常陌生,那么它是什么,又有什么用途呢?
**之前的问题**
Web应用程序变得越来越强大,例如新增了音视频处理、WebSocket等多个功能特性。毫无疑问,如果Javascript能够快速方便的操作原始二进制数据会相当的有用。过去,我们必须要把原始数据当作字符串来处理,并且使用charCodeAt方法来从数据缓冲区中读取字节。
但是这种方法需要多次转换数据(尤其在二进制数据不是字节格式的数据时,例如32位整数或者浮点数),所以非常慢而且容易出错。
Javascript需要一种机制来更有效的访问原始的二进制数据,由此产生了类型数组。
**定义**
其实除了Javascript,类型数组在其他很多语言中也有。它是一种数组,只有一种变量的类型。例如,一个float类型的数组将只包含浮点数而不能混用字符串和浮点数。此外,一个类型数组在初始化后不能改变大小。它看起来形式和普通Javascript数组很像,但是数据格式是一致和同一类型的(例如声音或者像素点的缓冲数据)。
类型数组的规范参见[这里](http://www.khronos.org/registry/typedarray/specs/latest/)。这个规范实质上定义了一种arrayBuffer类型,相当于一个普通的定长二进制缓冲区。我们不能直接访问和操作arrayBuffer的内容,而需要类型数组来创建arrayBuffer的视图(从技术上来说,类型数组等同于arrayBuffer,因为它们本质上是一样的)。例如,要访问32位有符号整数数组作为缓冲区,会创建一个Int32Array的类型数组来指向arrayBuffer。
多个类型数组视图可以指向同一个arrayBuffer,采用不同的类型、不同的长度以及不同的位移。例如下面的代码:
~~~
// 创建一个8字节的ArrayBuffer
var b = new ArrayBuffer(8);
// 创建一个指向b的视图v1,采用Int32类型,开始于默认的字节索引0,直到缓冲区的末尾
var v1 = new Int32Array(b);
// 创建一个指向b的视图v2,采用Uint8类型,开始于字节索引2,直到缓冲区的末尾
var v2 = new Uint8Array(b, 2);
// 创建一个指向b的视图v3,采用Int16类型,开始于字节索引2,长度为2
var v3 = new Int16Array(b, 2, 2);
~~~
上述代码里变量的数据结构如下所示。
![](https://box.kancloud.cn/2016-08-09_57a9a2e357378.jpg)
变量的数据结构
类型数组包括以下几种类型:
<table border="1" cellspacing="0" cellpadding="0"><tbody><tr><td valign="top" style="background:#4F81BD;"><p align="left"><strong>名称</strong></p></td> <td valign="top" style="background:#4F81BD;"><p align="left"><strong>大小 (以字节为单位)</strong></p></td> <td valign="top" style="background:#4F81BD;"><p align="left"><strong>说明</strong></p></td> </tr><tr><td valign="top"><p align="left"><strong>Int8Array</strong></p></td> <td valign="top"><p align="left">1</p></td> <td valign="top"><p align="left">8位有符号整数</p></td> </tr><tr><td valign="top"><p align="left"><strong>Uint8Array</strong></p></td> <td valign="top"><p align="left">1</p></td> <td valign="top"><p align="left">8位无符号整数</p></td> </tr><tr><td valign="top"><p align="left"><strong>Int16Array</strong></p></td> <td valign="top"><p align="left">2</p></td> <td valign="top"><p align="left">16位有符号整数</p></td> </tr><tr><td valign="top"><p align="left"><strong>Uint16Array</strong></p></td> <td valign="top"><p align="left">2</p></td> <td valign="top"><p align="left">16位无符号整数</p></td> </tr><tr><td valign="top"><p align="left"><strong>Int32Array</strong></p></td> <td valign="top"><p align="left">4</p></td> <td valign="top"><p align="left">32位有符号整数</p></td> </tr><tr><td valign="top"><p align="left"><strong>Uint32Array</strong></p></td> <td valign="top"><p align="left">4</p></td> <td valign="top"><p align="left">32位无符号整数</p></td> </tr><tr><td valign="top"><p align="left"><strong>Float32Array</strong></p></td> <td valign="top"><p align="left">4</p></td> <td valign="top"><p align="left">32位浮点数</p></td> </tr><tr><td valign="top"><p align="left"><strong>Float64Array</strong></p></td> <td valign="top"><p align="left">8</p></td> <td valign="top"><p align="left">64位浮点数</p></td> </tr></tbody></table>
类型数组实际上目前是作为WebGL的一部分来实现的(和它相关的还有File API),但是它可以用在任何地方。
下面我们来谈谈类型数组的优点和用途。
**优点**
1、 性能优秀
所有类型数组相关的文档都提到的重要一点是,类型数组比传统数组快的多,具有非常好的性能。因为类型数组实际上是作为一个固定的内存块来进行访问的,而传统的普通Javascript数组使用的是Hash查找方式(因为元素长度不定)。
这里有一个简单的测试结果,在Firefox4 Beta1版本,我们对比了一个普通数组和Float32Array数组在操作1亿个元素时每种操作所花费的时间。这个测试运行在Win7 64位、4G内存和Intel双核1.3G CPU的平台上。我们运行这个测试8次并使用其中最慢的一个时间。需要指出的是,普通Javascript数组的写入操作经常花费超过10秒钟,这会导致出现运行缓慢的脚本对话框。
<table border="1" cellspacing="0" cellpadding="0"><tbody><tr><td valign="top" style="background:#4F81BD;"><p><strong><span style="color:white;">操作</span></strong></p></td> <td valign="top" style="background:#4F81BD;"><p><strong><span style="color:white;">普通数组</span></strong></p></td> <td valign="top" style="background:#4F81BD;"><p><strong><span style="color:white;">Float32Array</span></strong></p></td> </tr><tr><td valign="top"><p><strong>写</strong></p></td> <td valign="top"><p>8947</p></td> <td valign="top"><p>1455</p></td> </tr><tr><td valign="top"><p><strong>读</strong></p></td> <td valign="top"><p>1948</p></td> <td valign="top"><p>1109</p></td> </tr><tr><td valign="top"><p><strong>循环复制</strong></p></td> <td valign="top"><p>>10,000</p></td> <td valign="top"><p>1969</p></td> </tr><tr><td valign="top"><p><strong>片段复制</strong></p></td> <td valign="top"><p>1125</p></td> <td valign="top"><p>503</p></td> </tr></tbody></table>
下面我们有一个关于普通数组、类型数组(arrayBuffer)以及imageData之间性能的比较,可以看到arrayBuffer会快得多。
![](https://box.kancloud.cn/2016-08-09_57a9a2e369afd.jpg)
性能优异的arrayBuffer
其实在类型数组之前,Javascript也支持二进制字节的数组,这就是imageData。imageData是Canvas元素2D上下文环境里定义的数据类型。当在Canvas 2D里调用getImageData或者createImageData方法时就创建了imageData。imageData的data属性是一个字节数组,它实际大小是图片宽*高的四倍(因为每个像素有R、G、B、A四个通道)。之前我们在《[用HTML5创建超酷图像灰度渐变效果](http://blog.csdn.net/hfahe/article/details/6208765)》这篇文章里就用到了imageData。
从图表数据来看,imageData和arrayBuffer在多个浏览器里创建的性能远远高于普通的数组([数据来源](http://blog.n01se.net/?p=248))。不过这里有一个问题,后面将会提到。
可以想见,因为优秀的性能表现,类型数组可以广泛的应用于Javascript图像以及视频的处理和压缩,还有一些需要复杂运算的场景例如MD5计算中,让功能可以更快速更高效的完成。例如我们先把imageData转换为类型数组以换取更快的执行速度,如下面的代码:
~~~
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的[演示](http://visual-demos.dev.concord.org/seasons/earth/model2d.html),用于对比普通数组和类型数组的性能,大家可以自行体验。
![](https://box.kancloud.cn/2016-08-09_57a9a2e37f51f.jpg)
Energy2D演示
第二个示例是使用类型数组、FileAPI以及Web Workers实现的SHA1[在线计算器](http://antimatter15.github.com/js-typed-array-sha1/),它的性能相当出色。正是在类型数组的支持下,Javascript执行SHA1、MD5这样复杂运算的速度变得越来越快。
![](https://box.kancloud.cn/2016-08-09_57a9a2e392e7d.jpg)
类型数组支持的在线SHA1计算器
2、 二进制支持
上文曾经提到类型数组最主要的特点是支持二进制数据。的确,现在HTMl5的许多API涉及音视频和实时通信,这些功能经常依赖于二进制文件格式,例如MP3音频、MP4视频和PNG图像。二进制格式对于减少带宽,提高性能,以及与现有文件格式互相转换来说非常重要。
类型数组使得Web应用可以使用多种二进制文件格式和直接操作文件的二进制内容,例如从现有的媒体文件中提取数据。
在IE10上,已经提供了类型数组的支持(支持WebGL其实是微软非常纠结的事情)。我们可以看看微软所提供的二进制文件检测器的[例子](http://ie.microsoft.com/testdrive/HTML5/TypedArrays/):
![](https://box.kancloud.cn/2016-08-09_57a9a2e3a8074.jpg)
在这个示例里,我们可以获取音乐文件的ID3头,视频文件的原始字节数据,以及附加文件的格式。它的核心代码如下:
~~~
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-你画我猜](http://game.163.com/12/0322/17/7T7G2FL200314OSE.html),我们可以用这种方式实现类似的WebApp),然后客户端再把Canvas数据还原为PNG图片。采用这种方式发送图像数据比起base64编码来更有效率(数据小33%,而且更利于序列化和存储)。”
![](https://box.kancloud.cn/2016-08-09_57a9a2e3c40a5.jpg)
创造了历史的应用-你画我猜
WebSocket支持二进制数据传输,对于WebSocket服务器来说,使用二进制数据会比UTF-8更为简单,不过现在浏览器支持方面还有问题。
示例里实现将Canvas数据转换为二进制格式的代码如下:
~~~
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中。
~~~
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》。可以看到里面利用类型数组发送图片的代码如下:
~~~
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其实就是为了性能敏感的功能诞生的,理论上能够提供更快的读取和写入速度。这有极大可能是目前浏览器厂商对于二进制数组优化不足造成的。我希望浏览器未来对于类型数组能有更好的支持。
![](https://box.kancloud.cn/2016-08-09_57a9a2e3db1fa.jpg)
某些操作和浏览器下,类型数组性能反而更低
**总结**
随着HTML5 Canvas、WebSocket等新特性的出现,WebApp能做的事情越来越多,同时Web App对于性能要求也越来越高,Javascript类型数组正是在这种情况下应运而生的。随着IE、Chrome、Opera等主流浏览器逐步提供对它的全面支持,以及可预期的性能优化,它将会发挥越来越重要的作用。
**附:[类型数组的浏览器支持情况](http://caniuse.com/#feat=typedarrays)**
转载请注明:来自[蒋宇捷的博客](http://blog.csdn.net/hfahe)
- 前言
- AutoPager的简单实现
- 利用CSS3特性巧妙实现漂亮的DIV箭头
- IE9在Win7下任务栏新特性简介
- 浏览器九宫格的简单实现
- Raphael js库简介
- 使用CSS3构建Ajax加载动画
- 用CSS3创建动画价格表
- 用CSS3实现浏览器的缩放功能
- 用纯CSS3实现QQ LOGO
- 用CSS3创建旋转载入器
- 使用Javascript开发移动应用程序
- 用HTML5创建超酷图像灰度渐变效果
- 使用CSS3创建文字颜色渐变(CSS3 Text Gradient)
- 仅用CSS创建立体旋转幻灯片
- 如何创建跨浏览器的HTML5表单
- 用CSS3实现动画进度条
- HTML5 Guitar Tab Player
- 奇妙的HTML5 Canvas动画实例
- 谈HTML5和CSS3的国际化支持
- 实现跨浏览器的HTML5占位符
- 前端开发必备工具:WhatFont Bookmarklet-方便的查询网页上的字体
- 使用HTML5和CSS3来创建幻灯片
- HTML5之美
- 如何使用HTML5创建在线精美简历
- 以小见大、由浅入深-谈如何面试Javascript工程师
- 快速入门:HTML5强大的Details元素
- 用CSS3实现图像风格
- HTML5视频字幕与WebVTT
- 用纯CSS3实现Path华丽动画
- 用3个步骤实现响应式网页设计
- 遇见CSS3滤镜
- 关于CSS3滤镜的碎念
- 用纯CSS3绘制萌系漫画人物动态头像
- CSS3新的鼠标样式介绍
- 用HTML5献上爱的3D玫瑰
- 对HTML5 Device API相关规范的解惑
- 如何使用HTML5实现拍照上传应用
- 2012第一季度国外HTML5移动开发趋势
- HTML5新特性:范围样式
- 百度开发者大会-《用HTML5新特性开发移动App》PPT分享
- Chrome 19对于HTML5最新支持的动态:电池状态API,全屏API,震动API,语音API
- 遇见Javascript类型数组(Typed Array)
- 用HTML5 Audio API开发游戏音乐
- 用HTML5实现人脸识别
- 用Javascript实现人脸美容
- Chrome 20对于HTML5最新支持的动态:颜色输入,网络信息API,CSS着色器
- 用HTML5实现手机摇一摇的功能
- 用HTML5实现iPad应用无限平滑滚动
- 用非响应式设计构建跨端Web App
- 了解SVG
- HTML5图像适配介绍
- HTML5安全:内容安全策略(CSP)简介
- HTML5安全:CORS(跨域资源共享)简介
- 用CSS3 Region和3D变换实现书籍翻页效果
- 谈谈移动App的思维误区
- Chrome新特性:文件夹拖拽支持
- 《关注HTML5安全》
- HTML5安全风险详析之一:CORS攻击
- HTML5安全风险详析之二:Web Storage攻击
- HTML5图像适配最新进展:响应式图片规范草案
- HTML5移动Web App相关标准状态及路线图
- HTML5安全风险详析之三:WebSQL攻击
- Chrome引入WebRTC支持视频聊天App
- HTML5安全风险详析之四:Web Worker攻击
- HTML5安全风险详析之五:劫持攻击
- HTML5安全风险详析之六:API攻击
- HTML5安全攻防详析之七:新标签攻击
- 在iOS Safari中播放离线音频
- 使用WebRTC实现远程屏幕共享
- Firefox、Android、iOS遇见WebRTC
- HTML5光线传感器简介
- HTML5安全攻防详析之八:Web Socket攻击
- HTML5安全攻防详析之完结篇:HTML5对安全的改进
- 激动人心!在网页上通过语音输入文字 - HTML5 Web Speech API介绍
- Web滚动性能优化实战
- 用CSS3设计响应式导航菜单
- 用HTML5构建高性能视差网站
- 漫谈@supports与CSS3条件规则
- HTML5下载属性简介
- 如何开发优秀的HTML5游戏?-迪斯尼《寻找奥兹之路》游戏技术详解(一)
- 如何开发优秀的HTML5游戏?-迪斯尼《寻找奥兹之路》游戏技术详解(二)
- 趋势:Chrome为打包应用提供强大新特性
- 从HTML5移动应用现状谈发展趋势
- 基于HTML5的Web跨设备超声波通信方案