前言
判断文件类型,有种很简单的方法,就是直接识别文件后缀,如果后缀是什么就是什么文件了。但是这样会有缺点,当文件的后缀名被修改了之后,会把文件识别成另外一种类型,然而文件的内容本身并没有变。
这个时候我们就需要直接读取文件内容,而不是仅通过名字来判断文件的类型。
怎么去做呢?
好在JS本身提供了一个数据结构类型,里面可以二进制形式来储存文件的内容,我们通过读取这个数据结构,就能拿到文件的内容了。
这个数据结构是ArrayBuffer
本篇文章默认读者已经掌握了ArrayBuffer相关概念以及用法,如果没有,建议看我这篇文章
判断思路
通过文件的内容来判断的依据是magic number,magic number是文件中的常量,不随文件内容的变更而变化。每个文件的magic number都是不一样的。所以我们可以通过它来判断文件的类型
- 现将文件转成ArrayBuffer格式
- magic number通常是文件的开头,或者文件的结尾,我们取出文件开头的几个数据,或者结尾的几个数据
- 如果这几个数据符合image/png的magic number,文件类型就是png了;如果符合image/jpeg,文件类型就是jpg了
实操
创建一个html文件,并且其中有读取文件的input标签
<input type="file" id="getFile" />
给标签加上事件
getFile.onchange = (e) => {
const file = getFile.files[0];
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
console.log(reader.result);
};
};
我们获取到了文件实例对象file之后,用FileReader实例去读取它,并将其转成ArrayBuffer类型。在onload回调函数中,可以通过reader的result属性拿到这个ArrayBuffer
下面我们可以开始着手判断,先以image/jpeg文件开始
image/jpeg的magic number格式:文件以" FF D8 "开头,并且以 " FF D9 "结束
reader.onload = function () {
const type = isJpeg(reader.result);
console.log(type);
};
function isJpeg(buffer){
var uint8Array = new Uint8Array(buffer);
var len = uint8Array.length;
if (
numToHex(uint8Array[0]) === "FF" &&
numToHex(uint8Array[1]) === "D8" &&
numToHex(uint8Array[len - 2]) === "FF" &&
numToHex(uint8Array[len - 1]) === "D9"
) {
return "jpeg";
}
return null;
}
//将十进制的number类型数据转成十六进制表示的字符串
function numToHex(number) {
let result = number.toString(16);
return result.padStart(2, "0").toUpperCase();
}
- 不能直接读取ArrayBuffer中的内容,必须通过视图,这里用了Uint8Array
- 我们首先拿了文件开头的两个字节,看是否为" FF D8 ",然后再拿结尾的两个字节,看是否为" FF D9 "
- 如果匹配成功,就返回" jpeg ";否则返回 null
- 因为用视图中直接读取ArrayBuffer中的内容,得到的是十进制的数字。比对magic number需要转成十六进制。
- JS转十六进制需要注意两个地方:
- 不补零。当十进制10转成十六进制,会得到a,但我们需要 0a。也就是比对magic number的时候,是以一个字节作为基本单位的,所以补零是必要的。例子中用的补零方法是原生的String.padStart()。
- 大小写。JS中十六进制是以小写格式存在的,在和magic number比对的时候,需要注意大小写
再来判断image/png文件
image/png的magic number格式: 文件以" 89 50 4E 47 0D 0A 1A 0A "开头
reader.onload = function () {
const type = isPng(reader.result);
console.log(type);
};
function isPng(buffer){
var uint8Array = new Uint8Array(buffer);
var bufferLen = uint8Array.length;
const magic = ["89", "50", "4E", "47", "0D", "0A", "1A", "0A"];
var magicLen = magic.length;
var isMatch = true;
for (var i = 0; i < magicLen; i++) {
if (magic[i] !== numToHex(uint8Array[i])) {
isMatch = false;
break;
}
}
if (isMatch) {
return "png";
}
}
匹配逻辑简单,就不解释了
整理下代码
我们已经完成了jpg和png两种文件的类型判断,我们可以通过相同的方式来判断其他类型的文件。这个就交给同学们自己去完成了
上面的代码写得不太好,所以我把代码整理了一下。下面是完整版的代码
getFile.onchange = function(){
// console.log(getFile.files);
const file = getFile.files[0];
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
var type = judgeType(reader.result);
console.log(type);
};
};
// 判断文件类型
function judgeType(buffer) {
var keys = Object.keys(getType);
for (var i = 0; i < keys.length; i++) {
var type = getType[keys[i]](buffer);
if (type) {
return type;
}
}
return null;
}
var getType = {
jpeg: function (buffer) {
var uint8Array = new Uint8Array(buffer);
var len = uint8Array.length;
if (
numToHex(uint8Array[0]) === "FF" &&
numToHex(uint8Array[1]) === "D8" &&
numToHex(uint8Array[len - 2]) === "FF" &&
numToHex(uint8Array[len - 1]) === "D9"
) {
return "jpeg";
}
return null;
},
png: function (buffer) {
var uint8Array = new Uint8Array(buffer);
var bufferLen = uint8Array.length;
var magic = ["89", "50", "4E", "47", "0D", "0A", "1A", "0A"];
var magicLen = magic.length;
var isMatch = true;
for (var i = 0; i < magicLen; i++) {
if (magic[i] !== numToHex(uint8Array[i])) {
isMatch = false;
break;
}
}
if (isMatch) {
return "png";
}
return null;
},
};
function numToHex(number) {
let result = number.toString(16);
return result.padStart(2, "0").toUpperCase();
}
- 将判断类型的函数放在了对象里面
- 增加了一个judgeType,作用是轮询匹配文件类型,如果匹配成功就返回类型字符串
视频版B站链接
本篇的实战例子是来自B站up主【狗胖爱棉花】,这是链接。
视频中有对上面例子更生动丰富的讲解
总结:
- 通过后缀名判断文件类型不靠谱,需要用magic number来判断
- JS提供了ArrayBuffer,让我们可以读取二进制格式的文件内容
- 文中有写的不明白的地方,评论告诉我