1.二进制数据
在 JavaScript 中有很多种二进制数据格式,比如:ArrayBuffer
,Uint8Array
,DataView
,Blob
,File
及其他。
2.ArrayBuffer
基本的二进制对象是 ArrayBuffer
—— 对固定长度的连续内存空间的引用。ArrayBuffer
是核心对象,是所有的基础,是原始的二进制数据。
let buffer = new ArrayBuffer(16);// 分配一个 16 字节的连续内存空间,并用 0 进行预填充。
console.log(buffer);// 内部是空值,仅开辟了空间
要注意的是:
ArrayBuffer
不是某种东西的数组(array 只是形容,表这个数据是连续的字节流)
让我们先澄清一个可能的误区。ArrayBuffer
与 Array
没有任何共同之处:
- 它的长度是固定的,我们无法增加或减少它的长度。
- 它正好占用了内存中的那么多空间。
- 要访问单个字节,需要另一个“视图”对象,而不是
buffer[index]
。
可以查看打印的结果:
打印结果包括了一个8位的视图内容(视图是将字节流可视化,让我们能够操作这个字节空间),还有字节流的长度。
可以看到我们并不能直接对ArrayBuffer进行操作,连打印结果都是转换成视图展示。而真正要对ArrayBuffer进行操作也正是需要视图(view)
3.视图(view)
操作 ArrayBuffer
,我们需要使用“视图”对象。
视图对象本身并不存储任何东西。它是一副“眼镜”,透过它来解释存储在 ArrayBuffer
中的字节,例如:
Uint8Array
—— 将ArrayBuffer
中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,因此只能容纳那么多)。这称为 “8 位无符号整数”。Uint16Array
—— 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。Uint32Array
—— 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。Float64Array
—— 将每 8 个字节视为一个5.0x10-324
到1.8x10308
之间的浮点数。
// 通过视图(view)来对二进制数据操作
let view = new Uint32Array(buffer); // 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”
console.log("视图:",view,"视图存储整数的个数:",view.length,"视图检查的二进制数据流的字节长度:",view.byteLength);// 类数组
view[0] = 1000;
console.log(view,buffer);// 第一个位置存入了100
通过view我们可以直观的看到ArrayBuffer的空间大小和内容,以及存放遍历数据(类数组)
4.TypedArray
所有这些视图(Uint8Array
,Uint32Array
等)的通用术语(总称)是 TypedArray。它们共享同一方法和属性集,以下都属于TypedArray
Uint8Array
,Uint16Array
,Uint32Array
—— 用于 8 位、16 位和 32 位无符号整数。Uint8ClampedArray
—— 用于 8 位整数,在赋值时便“固定”其值。Int8Array
,Int16Array
,Int32Array
—— 用于有符号整数(可以为负数)。Float32Array
,Float64Array
—— 用于 32 位和 64 位的有符号浮点数。
注意,没有名为 TypedArray
的构造器(以下构造方法只是引用,并没有new TypedArray的构造方法,应该使用new Uint32Array,new Uint8Array),它只是表示 ArrayBuffer
上的视图之一的通用总称术语:Int8Array
,Uint8Array
及其他,当你看到 new TypedArray
之类的内容时,它只是代替 new Int8Array(8位整数)
、new Uint8Array
(8位无符号整数)及其他中之一。
类型化数组的行为类似于常规数组:具有索引,并且是可迭代的。
它有5中构造方法
new TypedArray(buffer, [byteOffset], [length]);
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();
-
如果给定的是
ArrayBuffer
参数,则会在其上创建视图。上面已经用过这个语法了。
参数1:ArrayBuffer,一个二进制数据对象,参数2:可选,
给定视图读取的起始位置 (默认为 0),参数3:可选, 视图读取的长度(默认至 buffer 的长度)
let viewT1 = new Uint32Array(buffer,0,2);
console.log(viewT1);
对比默认的参数,这里是从第0位开始读取2位数据。
2.如果给定的是 Array
,或任何类数组对象,则会创建一个相同长度的类型化数组,并复制其内容,我们可以使用它来预填充数组的数据
let viewT2 = new Uint8Array([0,1,2,3]);
console.log(viewT2);
3.如果给定的是另一个 TypedArray
,也是如此:创建一个相同长度的类型化数组,并复制其内容。
let viewT3 = new Uint16Array(viewT1);
console.log(viewT3);
可以看到直接复制了一份viewT1的内容,但是转换了格式(32->16)
4.对于数字参数 length
—— 创建指定长度的类型化数组,并用0默认填充内容。
let viewT4 = new Uint8Array(6);
console.log(viewT4);
5.不带参数的情况下,创建长度为零的类型化数组,定义变量类型。
TypedArray
的属性:
view.buffer
—— 读取的对应ArrayBuffer
。view.byteLength
——ArrayBuffer
的长度。
let view = new Uint32Array(buffer); // 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”
console.log("视图:",view,"视图存储整数的个数:",view.length,"视图检查的二进制数据流的字节长度:",view.byteLength,"视图读取的二进制数据流:",view.buffer);// 类数组
越界行为
当对视图进行转换时,可能出现长度越界,例如,我们尝试将 256 放入 Uint8Array
。256 的二进制格式是 100000000
(9 位),但 Uint8Array
每个值只有 8 位,最终数字1会被移除,只保留后面的8个0,如果刚好1是符号位,那么就可能会出现正负转换(这就是计算时数据太大,正数加成负数的原因)
256和257最终变成 0和1
TypedArray
的方法:
TypedArray
具有常规的 Array
方法,但有个明显的例外。我们可以遍历(iterate),map
,slice
,find
和 reduce
等:
arr.set(fromArr, [offset])
从offset
(默认为 0)开始,将fromArr
中的所有元素复制到arr
。arr.subarray([begin, end])
创建一个从begin
到end
(不包括)相同类型的新视图。这类似于slice
方法(同样也支持),但不复制任何内容 —— 只是创建一个新视图,以对给定片段的数据进行操作。
但有几件事我们做不了:
- 没有
splice
—— 我们无法“删除”一个值,因为类型化数组是缓冲区(buffer)上的视图,缓冲区表示的计算机中的0,1数据,这是硬件决定的,我们不能将硬件删除。我们所能做的就是分配一个零值表示这个数据是空的,只能置零不能删除 - 无
concat
方法。
// (销毁数据)遍历视图,将二进制数据置零
function delData(view){view.map((item,index,array)=>{array[index] = 0;});
}
delData(view);
console.log(view);
这样就实现了删除数据的效果
5.DataView
DataView是在 ArrayBuffer
上的一种特殊的超灵活“未类型化”视图。它允许以其他任何格式(8位,16位,32位,等)访问任意长度的数据。
对于类型化的数组,构造器决定了其格式,只能访问当前格式的数据(8位的数据的数组,只能拿到8位的数据)
通过 DataView
,我们可以使用 .getUint8(i)
或 .getUint16(i)
之类的方法访问数据。我们在调用方法时选择格式,而不是在构造的时候。
new DataView(buffer, [byteOffset], [byteLength])
buffer —— 底层的 ArrayBuffer。与类型化数组不同,DataView 不会自行创建缓冲区(buffer)。我们需要事先准备好。
byteOffset —— 视图的读取的起始位置(默认为 0)。
byteLength —— 视图的读取长度(默认至 buffer 的末尾)。
// 4 个字节的二进制数组,每个都是最大值 255,一串都为1的二进制数据
let bufferData = new Uint8Array([255, 255, 255, 255]).buffer;// 通过视图得到二进制数据
console.log(bufferData);let dataView = new DataView(bufferData);
console.log(dataView.getUint8(0),dataView.getUint16(0),dataView.getUint32(0))
dataView.setUint32(0, 0); // 将 4 个字节的数字设为 0,即将所有字节都设为 0
console.log(dataView.getUint32(0));
用 DataView
可以轻松访问一个ArrayBuffer 的任意视图格式(8位,16位,32位,等)的内容,这拥有很高的灵活性
6.总结
ArrayBuffer
是核心对象,是对固定长度的连续内存区域的引用,几乎任何对 ArrayBuffer
的操作,都需要一个视图。它可以是 TypedArray,或者
或 DataView
完整代码和结果
let buffer = new ArrayBuffer(16);// 分配一个 16 字节的连续内存空间,并用 0 进行预填充。
console.log(buffer);// 内部是空值,仅开辟了空间
// 通过视图(view)来对二进制数据操作
let view = new Uint32Array(buffer); // 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”
console.log("视图:",view,"视图存储整数的个数:",view.length,"视图检查的二进制数据流的字节长度:",view.byteLength,"视图读取的二进制数据流:",view.buffer);// 类数组
view[0] = 1000;
console.log(view,buffer);// 第一个位置存入了100// 视图的构造方法
let viewT1 = new Uint32Array(buffer,0,2);
console.log(viewT1);let viewT2 = new Uint8Array([0,1,2,3]);
console.log(viewT2);let viewT3 = new Uint16Array(viewT1);
console.log(viewT3);let viewT4 = new Uint8Array(6);
console.log(viewT4);// (销毁数据)遍历视图,将二进制数据置零
function delData(view){view.map((item,index,array)=>{array[index] = 0;});
}
console.log(view);
delData(view);
console.log(view);console.log("------------");// 4 个字节的二进制数组,每个都是最大值 255,一串都为1的二进制数据
let bufferData = new Uint8Array([255, 255, 255, 255]).buffer;// 通过视图得到二进制数据
console.log(bufferData);let dataView = new DataView(bufferData);
console.log(dataView.getUint8(0),dataView.getUint16(0),dataView.getUint32(0))
dataView.setUint32(0, 0); // 将 4 个字节的数字设为 0,即将所有字节都设为 0
console.log(dataView.getUint32(0));