JavaScript类型化数组(二进制数组)
0、前言
对于前端程序员来说,平时很少和二进制数据打交道,所以基本上用不到ArrayBuffer,大家对它很陌生,但是在使用WebGL的时候,ArrayBuffer无处不在。浏览器通过WebGL和显卡进行通信,它们之间会发生大量的、实时的数据交互,对性能的要求特别高,它们之间的数据通信必须是二进制的才能满足性能要求,而不能是传统的文本格式。文本格式传递一个 32 位整数,两端的 JavaScript 脚本与显卡都要进行格式转化,将非常耗时。类型化数组的诞生就是为了能够让开发者通过类型化数组来操作内存,大大增强了JavaScript处理二进制数据的能力。
JavaScript类型化数组将实现拆分为缓冲和视图两部分。一个缓冲(ArrayBuffer)描述的是内存中的一段二进制数据,缓冲没有格式可言,并且不提供机制访问其内容。为了访问在缓存对象中包含的内存,你需要使用视图。视图可以将二进制数据转换为实际有类型的数组。一个缓冲可以提供给多个视图进行读取,不同类型的视图读取的内存长度不同,读取出来的数据格式也不同。缓冲和视图的工作方式如下图所示:
1、缓冲(ArrayBuffer)和视图
ArrayBuffer是一个构造函数,可以分配一段可以存放数据的连续内存区域。
var buffer = new ArrayBuffer(8);
上面代码生成了一段8字节的内存区域,每个字节的值默认都是0。1 字节(Byte) = 8 比特(bit),1比特就是一个二进制位(0 或 1)。上面代码生成的8个字节的内存区域,一共有 8*8=64 比特,每一个二进制位都是0。
为了读写这个buffer,我们需要为它指定视图。视图有两种,一种是TypedArray视图,它一共包括9种类型,还有一种是DataView视图,它可以自定义复合类型。 基础用法如下:
var dataView = new DataView(buffer);
dataView.getUint8(0) // var int32View = new Int32Array(buffer);
int32View[0] = 1 // 修改底层内存 var uint8View = new Uint8Array(buffer);
uint8View[0] // 1
视图类型 | 说明 | 字节大小 |
Uint8Array | 8位无符号整数 | 1字节 |
Int8Array | 8位有符号整数 | 1字节 |
Uint8ClampedArray | 8位无符号整数(溢出处理不同) | 1字节 |
Uint16Array | 16位无符号整数 | 2字节 |
Int16Array | 16位有符号整数 | 2字节 |
Uint32Array | 32位无符号整数 | 4字节 |
Int32Array | 32位有符号整数 | 4字节 |
Float32Array | 32位IEEE浮点数 | 4字节 |
Float64Array | 64位IEEE浮点数 | 8字节 |
下面来看一个完整的例子:
// 创建一个16字节长度的缓冲
var buffer = new ArrayBuffer(16);
// 创建一个视图,此视图把缓冲内的数据格式化为一个32位(4字节)有符号整数数组
var int32View = new Int32Array(buffer);
// 我们可以像普通数组一样访问该数组中的元素
for (var i = 0; i < int32View.length; i++) {
int32View[i] = i * 2;
}
// 运行完之后 int32View 为[0,2,4,6]
// 创建另一个视图,此视图把缓冲内的数据格式化为一个16位(2字节)有符号整数数组
var int16View = new Int16Array(buffer); for (var i = 0; i < int16View.length; i++) {
console.log(int16View[i]);
}
// 打印出来的结果依次是0,0,2,0,4,0,6,0
相信图片已经很直观的表达了这段代码的意思。这里应该有人会疑问,为什么2、4、6这三个数字会排在0的前面,这是因为x86的系统都是使用的小端字节序来存储数据的,小端字节序就是在内存中,数据的高位保存在内存的高地址中,数据的低位保存在内存的低地址中。就拿上面这段代码举例,上图中内存大小排列的顺序是从左向右依次变大,int32View[1]对应的4个字节,它填入的值是 10 (2的2进制表示),把0补齐的话就是 00000000 00000000 00000000 00000010(中间的分隔方便观看),计算机会倒过来填充,最终会成为 00000010 00000000 00000000 00000000。与小端字节序对应的就是大端字节序,它就是我们平时读数字的顺序。
2、实际场景
在WebGL中有这么一个需求,我要绘制一个带颜色的三角形,这个三角形有三个顶点,每个点有3个坐标和一个RGBA颜色,现在有了三角形的顶点和颜色数据,需要创建一个缓冲,把三角形的数据按顺序填入,然后传输给WebGL。目前的三角形数据是这样的:
var triangleVertices = [
// (x, y, z) (r, g, b, a)
0.0, 0.5, 0.0, 255, 0, 0, 255, // V0
0.5, -0.5, 0.0, 0, 250, 6, 255, // V1
-0.5, -0.5, 0.0, 0, 0, 255, 255 // V2
];
目标格式是一个ArrayBuffer,它的格式是这样的:
表示坐标的浮点数是32位的,占4个字节,表示颜色的正整数是8位的,占1个字节,因此我们需要创建两个视图来对这个缓冲进行赋值。
var triangleVertices = [
// (x, y, z) (r, g, b, a)
0.0, 0.5, 0.0, 255, 0, 0, 255, // V0
0.5, -0.5, 0.0, 0, 250, 6, 255, // V1
-0.5, -0.5, 0.0, 0, 0, 255, 255 // V2
]; var nbrOfVertices = 3; // 顶点数量
var vertexSizeInBytes = 3 * Float32Array.BYTES_PER_ELEMENT + 4 * Uint8Array.BYTES_PER_ELEMENT; // 一个顶点所占的字节数 3*4+4*1 = 16 var buffer = new ArrayBuffer(nbrOfVertices * vertexSizeInBytes); // 3 * 16 = 48 三个顶点一共需要的字节数
var positionView = new Float32Array(buffer);
var colorView = new Uint8Array(buffer); var positionOffsetInFloats = 0;
var colorOffsetInBytes = 12;
var k = 0;
// 用三角形数据填充arrayBuffer
for (var i = 0; i < nbrOfVertices; i++) {
positionView[positionOffsetInFloats] = triangleVertices[k]; // x
positionView[1 + positionOffsetInFloats] = triangleVertices[k + 1]; // y
positionView[2 + positionOffsetInFloats] = triangleVertices[k + 2]; // z
colorView[colorOffsetInBytes] = triangleVertices[k + 3]; // r
colorView[1 + colorOffsetInBytes] = triangleVertices[k + 4]; // g
colorView[2 + colorOffsetInBytes] = triangleVertices[k + 5]; // b
colorView[3 + colorOffsetInBytes] = triangleVertices[k + 6]; // a positionOffsetInFloats += 4; // 4个字节的浮点数循环一次要偏移4位
colorOffsetInBytes += 16; // 1个字节的整数循环一次要偏移16位
k += 7; // 原数组一次处理七个数值(三个坐标四个颜色)
}
这段代码运行完,就可以得到我们想要的ArrayBuffer。希望大家可以在浏览器控制台运行一下,然后看看positionView和colorView里面的数据验证一下。细心的小伙伴会发现,如果使用positionView访问颜色数据,或者colorView访问位置数据,得到的数据是“奇怪”的,不知道原因的读者朋友可以去了解一下原码、补码、IEEE浮点数相关的知识。
3、总结
类型化数组的内容还有很多,在这里我只重点介绍了一下缓冲和视图是如何一起合作来管理内存的。
类型化数组的出现最大的作用就是提升了数组的性能,js中Array的内部实现是链表,可以动态增大减少元素,但是元素多的话,性能会比较差,类型化数组管理的是连续内存区域,知道了这块内存的起始位置,可以通过起始位置+N * 偏移量(一次加法一次乘法操作)访问到第N个位置的元素,而Array的话就需要通过链表一个一个的找下去。
类型化数组的使用场景并不多,可以说是为WebGL量身定做的,不过还是希望你能在以后遇到大量数据的场景能够想起来JS的类型化数组这个功能。
JavaScript类型化数组(二进制数组)的更多相关文章
- JavaScript jQuery 中定义数组与操作及jquery数组操作
首先给大家介绍javascript jquery中定义数组与操作的相关知识,具体内容如下所示: 1.认识数组 数组就是某类数据的集合,数据类型可以是整型.字符串.甚至是对象Javascript不支持多 ...
- Javascript Jquery 中的数组定义与操作_子木玲_新浪博客
body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...
- ES6 二进制数组
二进制数组(ArrayBuffer对象.TypedArray视图和DataView视图)是JavaScript操作二进制数据的一个接口.这些对象早就存在,属于独立的规格(2011年2月发布),ES6将 ...
- JavaScript jQuery 中定义数组与操作及jquery数组操作 http://www.jb51.net/article/76601.htm
首先给大家介绍javascript jquery中定义数组与操作的相关知识,具体内容如下所示: 1.认识数组 数组就是某类数据的集合,数据类型可以是整型.字符串.甚至是对象Javascript不支持多 ...
- Javascript基础四(数组,字符,对象,日期)
第一节:数组 1.数组的概念及定义 可以存放一组数据: 当需要操作多个数据时: 2.数组的创建方式 var arr1 = [1,2,3]; //字面量方式 var arr2 ...
- es6二进制数组--基础
一.概念二进制数组由 ArrayBuffer对象 TypeArray 视图和DataView视图 三部分组成是javascript操作二进制数据的一个接口. 早在2011年2月就已经发布,但是由于ES ...
- JavaScript中常见的数组操作函数及用法
JavaScript中常见的数组操作函数及用法 昨天写了个帖子,汇总了下常见的JavaScript中的字符串操作函数及用法.今天正好有时间,也去把JavaScript中常见的数组操作函数及用法总结一下 ...
- JavaScript面向对象程序设计:数组
或许你会奇怪,面向对象的程序设计为什么从数组开始讲起?这是因为……其间的种种关系吧……嘿嘿,这里先卖个关子,先来看看我们熟悉的数组在JavaScript里面是什么样子的. 1. 创建数组 在J ...
- javascript中的稀疏数组(sparse array)和密集数组
学习underscore.js数组相关API的时候.遇到了sparse array这个东西,曾经没有接触过. 这里学习下什么是稀疏数组和密集数组. 什么是密集数组呢?在java和C语言中,数组是一片连 ...
随机推荐
- Linux 纯字符界面的玩法
Linux 纯字符界面的用途 装逼必备 省资源,服务器一般不安装图形界面 图形界面崩溃后紧急救援 进入字符界面的正确方式 目前新的 Linux 发行版基本上都使用 Systemd 作为 init 程序 ...
- MySQL 分区建索引
200 ? "200px" : this.width)!important;} --> 介绍 mysql分区后每个分区成了独立的文件,虽然从逻辑上还是一张表其实已经分成了多张 ...
- 【盛派周三分享-2019.2.20】开放分享内容,本期主题:《SCF、DDD及相关架构思想讨论》
“周三分享”是盛派网络约定的每周三晚上定时举办的内部分享活动,活动主要由技术人员分享各方面的技术主题,并由所有参与者围绕主题进行讨论.除技术话题外,也可能涉及到相关的设计.财税.金融.政策等方面的延伸 ...
- NeuChar 平台使用及开发教程(四):使用 NeuChar 的素材服务
各类公众号的功能之一就是为用户提供各类图文和多媒体的信息,因此素材是必不可少的. 进入 Neural Cell 设置界面,点击右侧[素材管理]按钮,进入素材管理界面. 目前系统提供了文本.多图文.图片 ...
- 实战深度学习(下)OpenCV库
在上一节中,我们讲到了OpenCV库的安装,现在我们来进行实战,看如何利用Python来调用OpenCV库. 一: 如果您的电脑是win10的系统,那么请您按下win键,再按下空格键,输入Python ...
- Java实现单例模式的9种方法
一. 什么是单例模式 因程序需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计. 二. 单例模式的特点 1. 单例模式只能有一个实例. 2. 单例类必须创建 ...
- [Swift]LeetCode497. 非重叠矩形中的随机点 | Random Point in Non-overlapping Rectangles
Given a list of non-overlapping axis-aligned rectangles rects, write a function pick which randomly ...
- [Swift]LeetCode933. 最近的请求次数 | Number of Recent Calls
Write a class RecentCounter to count recent requests. It has only one method: ping(int t), where t r ...
- 分布式_zookeeper
分布式协调服务-zookeeper 分布式环境的特点 1.分布性 2.并发性 程序运行过程中,并发性操作是很常见的.比如同一个分布式系统中的多个节点,同时访问一个共享资源.数据库.分布式存储 3.无序 ...
- CentOS Too Many Open Files 解决
问题 在使用 WRK 对应用服务进行压测的时候,提示 "too many open files" 信息,导致无法启动测试. 原因 CentOS 7.x 默认的打开文件数目限制为 1 ...