在 JavaScript 编程中,TypedArray 作为数据类型是比较常用的。这种数据类型的出现只是为了能够更方便地操作二进制数据。它提供了一种可以用来认定视图的构造函数,得以读入已知的类型,而不是十六进制数值的数组。下面我们就来一一学习 ES6 中的 TypedArray 数据类型。
1. TypedArray
TypedArray 是指一种只含有成员为特定数据类型的数组,其中特定数据类型是由 TypedArray 类型定义的。该类型有以下几个常用的子类型:
Int8Array
:8 位有符号整数Uint8Array
:8 位无符号整数Uint8ClampedArray
:8 位无符号整数(有约束)Int16Array
:16 位有符号整数Uint16Array
:16 位无符号整数Int32Array
:32 位有符号整数Uint32Array
:32 位无符号整数Float32Array
:32 位浮点数Float64Array
:64 位浮点数
我们需要注意的是,TypedArray 只能包含一个特定的数据类型,而不能同时包含多个不同类型的数据。
实现 TypedArray 的方式在其子类型内是基本一致的。在使用它们之前,必须要区分在构造函数中指定的 Buffers/ArrayBuffers 来存储数据以及这些 Buffer/ArrayBuffer 的字节偏移量(Buffer 不需要这个偏移量的信息)。
在 ES6 中使用 TypedArray 的一个常见的场景是处理音视频 Flow 的二进制数据。
2. Buffer/ArrayBuffer 特性
TypedArray 可以接受 ArrayBuffer 或 Buffer 这两个类型的数据源。 ArrayBuff 是一种可以让 TypedArray 映射到内存的字节数组,是无法直接使用的。而 Buffer 是 Node.js 赋予 JavaScript 的 Buffer 类,能够更加友好地支持二进制数据操作。
下面我们就来了解一下这两个类型的特性以及它们之间的区别。
2.1 ArrayBuffer
ArrayBuffer 是一个二进制数据缓存区,通常是不能直接操作的。如果你想在 ES5 或者之前的环境用 TypedArray 来读取二进制流,你需要自己实现 ArrayBuffer 的分配、增长、运维等。而到了 ES6 以后,基于 TypedArray 的语法糖就让 ArrayBuffer 操作变得简单了起来。
TypedArray 对 ArrayBuffer 实际上是要求对其进行一定程度的使用。其中,TypedArray 数组是在 ArrayBuffer 上导出的视图,以特定长度和字节偏移量构建出来的,可以直接读写 ArrayBuffer 的数据。
观察下面的代码:
var buffer = new ArrayBuffer(8); var dataview = new DataView(buffer); dataview.setInt8(0, 10); dataview.setInt8(1, 0x20); console.log(new Int16Array(buffer));
上面的代码会输出 [8250]
。这个结果是因为在这个例子中新建了一个 ArrayBuffer 和其 DataView。这个 DataView 的字节偏移量是从 TextView 开始的。Int16Array 会在 TextView 内的数据上完成位序列从低位到高位的合成。
2.2 Buffer
Node.js 中的 Buffer 是对 ArrayBuffer 的再次包裹。我们可以通过使用如下代码创建一个 Buffer:
var buf = new Buffer(16);
注意,这个版本的 Buffer 是有一些安全风险的,因为它默认会对内存池进行可写可读的授权。具有可执行代码的 JPEG 或 PNG 的 header,如果在其中寄有一个故障,就有可能越过 Boundary 覆盖一些开了 W^X 属性的内存区塊,从而控制计算机的权限。
这个安全风险只出现在推荐的转码方式汇总,其中会在含有大量内存分配的场景下失控,比如 zlib 初始化阶段中的大量内存分配等。
在 Node.js v8.3 以后的版本中,Buffer 的创建方式推荐是:
Buffer.alloc(16)
这个方式是通过系统程序来初始化内存池,如果系统程序逊于 v8.3 或者没有可用的内存池,系统程序在调用 gc 的时候也会主动去通知系统释放规格超出大小阈值的 Buffer,更加健壮。使用 Buffer.alloc 返回的对象同样可以像 ArrayBuffer 一样被使用。
2.3 区别与应用场景
使用 ArrayBuffer 能够做的事情,使用 Buffer 和 TypedArray 也能够做到,反之亦然,区别在于它们的应用场景和性能上的差异。下面我们来探讨一下这两个不同类型之间的区别。
2.3.1 数组复制
Buffer 的特别之处是它可以很容易地对二进制数据进行读写,而无需进行任何的索引运算。Buffer 是在 EventEmitter 上以可读流和可写流的形式实现的,是 Node.js 标准库中的内置类型。它能够在将 Buffer 写到流中之前进行加工,比如: 添加压缩、加密。在读取流中的 Buffer 时能够进行相反的加工,比如解压、解密。 Buffer 类型本身也提供了很多的 API,这些 API 是一些方便使用和优化性能的小工具。
下面是一个使用 Buffer 的示例。这个代码会把同样的数据复制给三个新的 Buffer 对象。
const buf1 = Buffer.alloc(10); const buf2 = Buffer.alloc(10,1); const buf3 = Buffer.allocUnsafe(10);
2.3.2 Array 的行为
TypedArray 是通常在 GPU、硬件级别处理程序和高端音视频的引擎中才会用到的高级工具。它是对 Array 的补充,但二者有许多明显的差异。在 Array 上执行很多通用转换都是比较容易的,因为 Array 具有高度灵活性。
意味着很多从 TS 转换过来的遗产代码不需要太多的重构以支持 TypedArray。这时,我们可以使用 TypedArray 来避免因移动一段数据而带来的内存和时间成本。
下面我们使用 TypedArray 将数组乘以一个常数。
const a = new Int16Array([1,2,3,4]); const b = new Int16Array(a.length); for (let i = 0; i < a.length; i++) { b[i] = a[i] * 2; } console.log(b);
上面的代码会输出 [2,4,6,8]
。
总的来说,如果我们需要在标准的数组结构上再做一些有用的附加操作,这时候就要使用 TypedArray 来解决问题。
3. 总结
TypedArray 在处理二进制数据时能够发挥很大的作用,不过其原理和使用方法较为复杂。熟悉了 ES6 中 TypedArray 的相关知识后可以让我们在处理此类问题时更加得心应手。同时,我们能够针对实际问题判断何时使用 TypedArray 以及何时使用 ArrayBuffer 和 Buffer,这样能够更好地节省计算机资源和提高我们的代码运行效率。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/64f95a7ff6b2d6eab30e08fc