By Noxxxx from https://www.noxxxx.com/?post_type=post&p=1465
欢迎分享与聚合,尊重版权,可以联系授权
上一篇文章从 W3C 草案的角度入手过了一遍 File API 的几个方法,这一篇尝试梳理一下二进制数据相关的一些方法,有 Blob、ArrayBuffer、Uint8Array、BufferSource 等。
用于对二进制数据进行操作的方法的描述:
ArrayBufferView
是所有这些视图的总称。BufferSource
是ArrayBuffer
或ArrayBufferView
的总称。
Blob
Blob
由一个可选的字符串 type
(通常是 MIME 类型)和 blobParts
组成。
ArrayBuffer
ArrayBuffer
对象用来表示对固定长度的连续内存空间的引用,它是一个字节数组,由于无法直接操作,需要通过类型数组对象或 DataView
对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
let buffer = new ArrayBuffer(16); // 创建一个长度为 16 个字节的 buffer
alert(buffer.byteLength); // 16
它会分配一个 16 字节的连续内存空间,并用 0 进行预填充。
ArrayBuffer
不是某种东西的数组, ArrayBuffer
与 Array
没有任何共同之处:
- 它的长度是固定的,我们无法增加或减少它的长度。
- 它正好占用了内存中的那么多空间。
- 要访问单个字节,需要另一个“视图”对象,而不是
buffer[index]
。
ArrayBuffer
是一个内存区域。它里面存储了什么?无从判断。只是一个原始的字节序列。
TypedArray
因为 ArrayBuffer 是一个原始的字节序列,不是所谓的“数组”,无法用下标来查看,因此需要使用 TypedArray 来实现访问,下面列的是具体的方法,它们统称为 TypeArray。
Uint8Array
—— 将ArrayBuffer
中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。这称为 “8 位无符号整数”。Uint16Array
—— 将每 2 个字节视为一个 0 到 65535 之间的整数。称为 “16 位无符号整数”。Uint32Array
—— 将每 4 个字节视为一个 0 到 4294967295 之间的整数。称为 “32 位无符号整数”。Float64Array
—— 将每 8 个字节视为一个5.0x10-324
到1.8x10308
之间的浮点数。
因此,一个 16 字节 ArrayBuffer
中的二进制数据可以解释为 16 个“小数字”,或 8 个更大的数字(每个数字 2 个字节),或 4 个更大的数字(每个数字 4 个字节),或 2 个高精度的浮点数(每个数字 8 个字节)。
let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 buffer
let view = new Uint32Array(buffer); // 将 buffer 视为一个 32 位整数的序列
alert(Uint32Array.BYTES_PER_ELEMENT); // 每个整数 4 个字节
alert(view.length); // 4,它存储了 4 个整数
alert(view.byteLength); // 16,字节中的大小
// 让我们写入一个值
view[0] = 123456;
// 遍历值
for(let num of view) {
alert(num); // 123456,然后 0,0,0(一共 4 个值)
}
转换逻辑:
let arr16 = new Uint16Array([1, 1000]); // 给定数组,根据数组 length 创建
let arr8 = new Uint8Array(arr16); // 根据下文的解释,这里只有 2 个字节,所以 1000 转成 2进制是无法存放的,故截取低 8 位
alert( arr8[0] ); // 1
alert( arr8[1] ); // 232,试图复制 1000,但无法将 1000 放进 8 位字节中
类型化数组的字节长度是 length
乘以单个 TypedArray.BYTES_PER_ELEMENT
中的字节数
let arr = new Uint16Array(4); // 为 4 个整数创建类型化数组
alert( Uint16Array.BYTES_PER_ELEMENT ); // 每个整数 2 个字节
alert( arr.byteLength ); // 8(字节中的大小)
下面是类型化数组的列表:
Uint8Array
,Uint16Array
,Uint32Array
—— 用于 8、16 和 32 位的整数。Uint8ClampedArray
—— 用于 8 位整数,在赋值时便“固定“其值(见下文)。
Int8Array
,Int16Array
,Int32Array
—— 用于有符号整数(可以为负数)。Float32Array
,Float64Array
—— 用于 32 位和 64 位的有符号浮点数。
越界行为
正如上面看到的,使用 Uint8Array
来存储 Uint16Array
是无法存储所有数据的,对于越界存储虽然不会报错。但是多余的位被切除。
TypedArray
具有常规的 Array
方法,但有个明显的例外。
我们可以遍历(iterate),map
,slice
,find
和 reduce
等。
但有几件事我们做不了:
- 没有
splice
—— 我们无法“删除”一个值,因为类型化数组是缓冲区(buffer)上的视图,并且缓冲区(buffer)是固定的、连续的内存区域。我们所能做的就是分配一个零值。 - 无
concat
方法。
还有两种其他方法:
arr.set(fromArr, [offset])
将fromArr
中从offset
(默认为 0)开始的所有元素复制到arr
。arr.subarray([begin, end])
创建一个从begin
到end
(不包括)相同类型的新视图。这类似于slice
方法(同样也支持),但不复制任何内容 —— 只是创建一个新视图,以对给定片段的数据进行操作。
有了这些方法,我们可以复制、混合类型化数组,从现有数组创建新数组,等。
DataView
DataView 方法更具备灵活性,可以对 Buffer 内容进行任何格式访问任何偏移量(offset)的数据。
new DataView(buffer, [byteOffset], [byteLength])
buffer
—— 底层的ArrayBuffer
。与类型化数组不同,DataView
不会自行创建缓冲区(buffer)。我们需要事先准备好。byteOffset
—— 视图的起始字节位置(默认为 0)。byteLength
—— 视图的字节长度(默认至buffer
的末尾)。
// 4 个字节的二进制数组,每个都是最大值 255
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
let dataView = new DataView(buffer);
// 在偏移量为 0 处获取 8 位数字
alert( dataView.getUint8(0) ); // 255
// 现在在偏移量为 0 处获取 16 位数字,它由 2 个字节组成,一起解析为 65535
alert( dataView.getUint16(0) ); // 65535(最大的 16 位无符号整数)
// 在偏移量为 0 处获取 32 位数字
alert( dataView.getUint32(0) ); // 4294967295(最大的 32 位无符号整数)
dataView.setUint32(0, 0); // 将 4 个字节的数字设为 0,即将所有字节都设为 0
至此,前端二进制相关的知识点介绍的差不多了,后面会有一个综合的案例,基于音频文件合并和衔接来做展开。