JS 二进制操作基础
前端要操作二进制,自然避不开这些类型 ArrayBuffer,Uint8Array,DataView,Blob,File等。不常用,所以我们先来了解一下他们的关系。
基本的二进制对象是 ArrayBuffer, 虽然有一个 Array ,但是和 Array 没什么关系。它是对固定长度的连续内存空间的引用。
既然 ArrayBuffer 只是一段内存引用,那么怎么来读写这段内存,就需要用到其他的几个类型,称之为视图(View)。如 Uint8Array,Uint 即为 无符号整型,8即为 8比特(bit)即1字节(byte)。
那么我们声明了一个let buffer = new ArrayBuffer(16)
即为一个16字节长度的内存引用。
我们通过 Uint8Array
来“看”它,它就是长度为16的一个数据数组,每个数据长度为1字节;
我们通过 Uint16Array
来“看”它,它就是长度为8的一个数据数组,每个数据长度为2字节;
这些统称为TypedArray
,不过不存在TypedArray
这个类型。
当然我们还有一种更加灵活的视图 DataView
, 他可以读取任意大小的一段内存数据。
具体的用法,我们结合下面的示例来讲。
怎么判断文件是不是PNG格式?
通常,我们判断一个文件是什么格式,是通过文件名后缀来判断的。但是后缀名是很容易被人为的修改的,难道修改了后缀名,文件的格式就变了吗? 这个当然是不会变的,因为文件的二进制数据并未被修改。那么软件是怎么来判断给定的二进制文件是否是自己支持的文件呢? 这个信息肯定是储存在二进制的信息中。
比如我们这里的PNG格式,在 PNG file spec 里面就规定了,PNG 文件的开头必须是这八个字节(byte,Uint8)的数据。
The first eight bytes of a PNG datastream always contain the following (decimal) values:
137 80 78 71 13 10 26 10
This signature indicates that the remainder of the datastream contains a single PNG image, consisting of a series of chunks beginning with an IHDR chunk and ending with an IEND chunk.
所以我们想判断一个文件是否真的是PNG文件,只需要校验文件开头的8个字节即可(当然不绝对,也不排除伪造的可能,但是伪造的成本比起改文件名后缀要高)。
实践一下
在浏览器怎么拿到文件二进制信息?
对于一个File
或Blob
对象,我们可以通过 FileReader
来读取到一个 ArrayBuffer
中。
async function fileToArrayBuffer(file) {
return new Promise((res, rej) => {
let fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = function () {
res(fileReader.result);
};
fileReader.onerror = rej;
});
}
怎么读取前8个字节?
这里就用到了,前面讲的 DataView
(当然,用TypedArray
也是可以的。
// DataView的构造函数,参数分别是
// 1. arraybuffer
// 2. start index
// 3. end index
// 这里我们只读取前8个字节。
const dataView = new DataView(arrayBuffer, 0, 8);
// 获取其中一个字节 Uint8
dataView.getUint8(index)
// 获取 Uint16
// ⚠️注意此时的 index 最大为3 ,长度为4。因为 Uint16长度 等于 2*Uint8的长度。
dataView.getUint16(index)
比较文件头
OK,文件的前8字节获取到了。那么我们只需要比较一下就可以判断是否是PNG文件了。
const PNG_SIG = [137, 80, 78, 71, 13, 10, 26, 10];
async function checkFile(file) {
const arrayBuffer = await fileToArrayBuffer(file);
for (let i = 0; i < 8; i++) {
console.log(i, PNG_SIG[i], dataView.getUint8(i));
if (PNG_SIG[i] !== dataView.getUint8(i)) return false;
}
return true;
}
完整代码在这里
思考
- 这里我是每次取出一个字节来比较,那么有没有更直接的简单的方法呢?
- 提示: Float64Array
- 你可以写出判断JPEG,GIF等文件的方法吗?