在日常开发或文件处理过程中,我们通常通过文件的后缀名(如 .mp3
、.mp4
、.jpg
等)来判断它的类型。然而,这种方式并不可靠。
举个例子:如果你将一个 xx.wav
文件手动改名为 xx.mp3
,虽然后缀变成了 MP3,但它本质上仍是 WAV 文件。为什么?因为计算机会根据文件内容的前几个字节(即文件头)来识别文件类型,这部分内容被称为 魔数(Magic Number) 或 文件签名。
魔数通常是某种固定的二进制序列,不同类型的文件有不同的魔数。借助这些魔数,我们可以更准确地判断文件的真实类型。
常见文件的魔数(Magic Number)
文件类型 | 魔数(十六进制) |
---|---|
JPEG 图片 | FF D8 FF DB / 49 46 00 01 等 |
PNG 图片 | 89 50 4E 47 0D 0A 1A 0A |
MP3 音频 | 49 44 33 / FF FB / FF F3 / FF F2 |
MP4 视频 | 66 74 79 70 69 73 6F 6D |
👉 想了解更多文件签名,请参考维基百科文档:List of file signatures
如何读取文件的二进制内容?
通过浏览器原生的 FileReader
API 来读取文件的原始二进制数据:
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = e => {
const arrBuffer = e.target!.result as ArrayBuffer;
// 后续处理逻辑
};
reader.onerror = err => {
throw new Error("文件读取失败: " + err);
};
ArrayBuffer 转十六进制字符串
为了匹配魔数,需要将二进制数据转换为十六进制字符串。
⚠️ 注意:
Array.from()
对于过大的 buffer(超过2^32
)会抛出invalid array length
错误。因此建议限制只读取前面几 KB 的内容用于判断类型即可。
function bufferToHex(buffer: ArrayBuffer, length = 64): string {
const uint8Array = new Uint8Array(buffer, 0, length);
return Array.from(uint8Array)
.map(byte => byte.toString(16).padStart(2, "0"))
.join("");
}
这里默认只取前 64 个字节,已足够判断大多数文件类型。
文件类型判断示例:识别 MP3 文件
可以预先定义一组魔数规则,然后使用正则匹配文件的十六进制前缀:
const Mp3MagicNumbers = [
"494433",
"FFFB",
"FFF3",
"FFF2",
"FFE2",
"FFE3",
"FFFA",
];
function isMp3(hex: string): boolean {
return Mp3MagicNumbers.some(magic => new RegExp("^" + magic, "i").test(hex));
}
完整调用流程:
reader.onload = e => {
const arrBuffer = e.target!.result as ArrayBuffer;
const hex = bufferToHex(arrBuffer);
const isMp3File = isMp3(hex);
console.log("Is MP3:", isMp3File);
};
结语
通过读取文件的魔数而不是简单依赖后缀名,可以大大提高我们判断文件类型的准确性,尤其是在涉及文件上传、格式校验、安全审查等场景中尤为重要。