Skip to content
Go back

如何判断没有后缀名的文件类型

Edit page

在日常开发或文件处理过程中,我们通常通过文件的后缀名(如 .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);
};

结语

通过读取文件的魔数而不是简单依赖后缀名,可以大大提高我们判断文件类型的准确性,尤其是在涉及文件上传、格式校验、安全审查等场景中尤为重要。


Edit page
Share this post on:

Previous Post
2024 年终总结
Next Post
如何管理多个 Git 身份