二进制操作时性能蛮高的。
- ArrayBuffer、Uint8Array、DataView、Blob、File 等。
ArrayBuffer 是基础的二进制对象——是一个指向一块连续内存区域的引用。
let buffer = new ArrayBuffer(16) // 创建一个长度为 16 的 buffer
buffer.byteLength // 16
代码执行后,会分配一块连续的、占据 16 字节长度的内存区域,每个字节被 0 填充。
注意:ArrayBuffer 不是数组
ArrayBuffer 对应一块内存区域。
为了操作 ArrayBuffer,我们需要使用“view”对象。
- Uint8Array——每一个字节用来表示一个数值,有效值从 0 到 255(一个字节是 8 位)。这些值被称为“8位无符号整数”
- Uint16Array——用两个字节表示一个整数,有效值从 0 到 65535。这些值被称为“16位无符号整数”
- Uint32Array——
- Float64Array——用八个字节表示一个浮点数。取值范围从 5.0x10-324 到 1.8x10308。
因此,一个包含 16 个字节的 ArrayBuffer 可以表示为 16 个“很小的数字”,或者 8 个较大的数字(每个两个字节),或者 4 个更大的数字(每个四个字节),或者 2 个高精度的浮点数(每个八个字节)。
ArrayBuffer 是二进制文件操作中,最底层、最原始的核心对象。
对二进制数据的进行的几乎任何操作,比如写和遍历,基本上都要通过 view 来实现的。例如:
let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 buffer
let view = new Uint32Array(buffer) // 将 buffer 作为 32 位整数操作
alert(Uint32Array.BYTES_PER_ELEMENT) // 每个整数占据 4 个字节
alert(view.length) // 4,存储了这么多整数
alert(view.byteLength) // 16,一共占据了这么多字节长度
// 我们写入一个值
view[0] = 123456
// 遍历出 view 中的值
for(let num of view) {
alert(num); // 123456, 然后是 0, 0, 0 (一共 4 个值)
}
TypedArray
上面讲的所有这些 view 的通用术语叫 TypedArray。它们共享同样的方法和属性。
它们类似于常规数组:有索引、可迭代。
TypedArray 构造器(比如 Int8Array 或者 Float64Array,这不重要)根据使用参数类型的不同而有不同的表现。
一共有五种创建方式:
new TypedArray(buffer, [byteOffset], [length]);
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();
- 如果提供的参数是 ArrayBuffer,则会在其基础之上创建 view。我们前面已经使用过了上面的语法。
可选参数 byteOffset、length 表示创建 view 时的起始索引(默认为 0)和长度(默认到 buffer 的最后一个字节),如此,创建的 view 对象仅覆盖 buffer
的一部分。
- 如果是 Array,或者是类数组(array-like)对象,那么结果会创建一个等长的、复制了原对象的内容的 TypedArray。
我们可以借助这个特点,用数组实现数据的预填:
let arr = new Uint8Array([0, 1, 2, 3])
alert(arr.length) // 4, 创建了一个等长的二进制数组
alert(arr[1]) // 1, 用 4 个字节填充规定的 4 个值(无符号 8 位整数)
- 如果提供的是 TypedArray,则所做的事情相同:结果会创建一个等长的、复制了原对象内容的 TypedArray。如果需要的话,这些值会被处理为新类型。
let arr16 = new Uint16Array([1, 1000]);
let arr8 = new Uint8Array(arr16);
alert( arr8[0] ); // 1
alert( arr8[1] ); // 232, 尝试复制 1000 的时候, 但 8 位字节无法表示 (后面解释)
- 对数值参数 length——会创建一个包含这么多元素的 TypedArray。而最终的字节长度等于 length 乘以每个元素的字节长度,可以由 TypedArray.BYTES_PER_ELEMENT 得到。
let arr = new Uint16Array(4);
alert(Uint16Array.BYTES_PER_ELEMENT); // 每个整数占据 2 个字节
alert(arr.byteLength); // 8(字节长度)
- 无参数的话,会创建一个长度为 0 的 TypedArray。
我们可以不使用 ArrayBuffer 直接创建一个 TypeArray,但要清楚的是,每一个 view 底层都是 ArrayBuffer。上述除第一个的其他创建方式,会自动创建 ArrayBuffer 的。
访问 ArrauBuffer,可使用下列属性:
arr.buffer
——对底层 ArrayBuffer 的引用。arr.byteLength
——底层 ArrayBuffer 占据的字节数。
let arr8 = new Uint8Array([0, 1, 2, 3]);
// 基于同一个底层 ArrayBuffer 数据的另一个 view
let arr16 = new Uint16Array(arr8.buffer);
- Uint8Array、Uint16Array、Uint32Array
- Uint8ClampedArray
- Int8Array、Int16Array、Int32Array
- Float32Array、Float64Array
超出边界后的行为
如果我们尝试将 256 放入 Uint8Array。256 的二进制形式为 100000000(共 9 位),但是 Unit8Array 只有 8 位表示每个值,有效范围从 0 到 255。
对于超出范围的值,只会保留存储最右边的 8 位,剩下则会切除:
结果得到了 0。
那么 257 呢,它的二进制形式为 100000001(共 9 位),只会存储最右边的 8 位,因此数组里会保留一个 1:
就是说,最后保留下来的值是数字对 28 取模后的结果。
demo:
let uint8array = new Uint8Array(16);
let num = 256;
alert(num.toString(2)); // 100000000 (二进制表示)
uint8array[0] = 256;
uint8array[1] = 257;
alert(uint8array[0]); // 0
alert(uint8array[1]); // 1
Uint8ClampedArray 与 Uint8Array 相比,特别的地方在于,对于大于 255 的值,保存为 255,对小于 0 的负值,保存的则是 0。
TypedArray 方法
TypedArray 具备有常规的数组方法。
我们可以遍历、map、slice、find、reduce 它。
但有些事情没法做:
- 无法 splice
- 无法使用 concat 方法
此外,还提供了额外的两个方法:
- arr.set(fromArr, [offset])
- arr.subArray([begin, end])
DateView
DateView 是一个超级灵活的“untyped”的 view。可以以任何格式和偏移量访问底层的 ArrayBuffer 数据。
- 使用 TypedArray 创建对象时,从名字上即可得知其格式。整个数组的表示都是统一的。第 i 个数就是 arr[i]。
- DateView 使用类似 .getUnit8(i) 或 .getUint16(i) 这些方法访问数据。我们是在调用的时候指定获取数据的格式,而非在创建的时候。
语法:
new DataView(buffer, [byteOffset], [byteLength])
- buffer——对象底层使用的 ArrayBuffer。不同于 TypedArray 的是,DataView 不能自行创建自己的 buffer,我们需要一个准备好的 buffer 创建一个 DataView 对象。
- byteOffset
- byteLength
例如,这里我们从同一个 buffer 中使用不同格式提取数字:
// 包含 4 字节的二进制数组,存储最大值 255
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// 以 8 位数字、偏移量 0 访问数据
alert( dataView.getUint8(0) ); // 255
// 现在再以 16 位数字、偏移量 0 访问数据,每个数字由 3 字节组成,拼在一起就成 65535 了
alert( dataView.getUint16(0) ); // 65535 (最大的 16 位无符号整数)
// 现在再以 32 位数字、偏移量 0 访问数据
alert( dataView.getUint32(0) ); // 4294967295 (最大的 32 位无符号整数)
dataView.setUint32(0, 0); // 设置 4 字节数值 0,导致所有字节值都变为 0 了
总结
ArrayBuffer 是一个指向一块连续内存区域的引用,是二进制文件操作中,最底层、最原始的核心对象。
几乎所有对 ArrayBuffer 的操作,都是通过 view 对象实现的。
- 可以是 TypedArray:
- Uint8Array、Uint16Array、Uint32Array
- Uint8ClampedArray
- Int8Array、Int16Array、Int32Array
- Float32Array、Float64Array
- 或者 DataView——以方法的形式指定特定格式的数据。比如 getUint8(offset)。
这里还有另外两个术语,描述操作二进制数据时会用到的:
- ArrayBufferView 是所有类型的 view 对象的总称
- BufferSource 是 ArrayBuffer 和 ArrayBufferView 的统称。
我们会在下一章里看到这些术语。BufferSource 是最长使用的术语之一。它表示“任意类型的二进制数据”——ArrayBuffer 或者基于其上的 view 对象。
(完)