使用 files.newinputstream() 读取文件头字节,依据 magic number 快速识别媒体类型:jpeg 读4字节、png 读8字节、gif 读6字节、mp4/mov 读12字节、avi 读12字节,配合 readnbytes 和预定义签名数组高效校验。
用 files.newinputstream() 读取大型媒体文件的二进制头信息,核心是「只读开头少量字节」,避免加载整个文件。java 的 inputstream 天然支持按需读取,配合标准文件头(magic number)比对,就能快速、低开销地校验真实类型。
明确要读多少字节:不同格式的 Magic Number 长度不同
媒体文件的真实类型识别依赖其文件头(前几个字节),常见格式所需最小读取长度如下:
-
JPEG:以
0xFF 0xD8开头,通常读前 4 字节即可确认(含 SOI + APP0 标记) -
PNG:固定为 8 字节签名
89 4E 47 0D 0A 1A 0A -
GIF:前 6 字节为
47 49 46 38 37 61或47 49 46 38 39 61(即 "GIF87a" 或 "GIF89a") -
MP4 / MOV:前 4 字节是 box size,接着 4 字节是 type(如
ftyp),一般读前 12 字节可定位 -
AVI:第 9–12 字节为
41 56 49 20("AVI "),需至少读 12 字节
用 try-with-resources 安全打开并限制读取长度
直接调用 Files.newInputStream(path) 返回一个可重复读取的流(注意不是所有实现都支持 mark/reset,但本地文件系统通常支持)。推荐用 InputStream.readNBytes(int)(Java 11+)或手动循环读取指定字节数:
Path path = Paths.get("video.avi");
byte[] header = new byte[12]; // 按需调整长度
try (InputStream is = Files.newInputStream(path)) {
int read = is.readNBytes(header, 0, header.length);
if (read
<p>⚠️ 注意:<code>readNBytes</code> 会阻塞直到读满或流结束,适合小头部;若需兼容 Java 8,可用 <code>is.read(byte[], off, len)</code> 并检查返回值是否等于预期长度。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill2915" title="Upload audio to AIOZ Stream"><img
src="https://img.php.cn/upload/skill/000/000/081/178938861927259.jpg" alt="Upload audio to AIOZ Stream" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill2915" title="Upload audio to AIOZ Stream" class="overflowclass">Upload audio to AIOZ Stream</a>
<p class="overflowclass">快速上传音频至 AIOZ Stream API。支持默认或自定义编码配置创建音频对象,上传文件并完成处理后返回音频链接。</p>
</div>
<a rel="nofollow" href="/xiazai/skill2915" title="Upload audio to AIOZ Stream" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<h3>比对 Magic Number:用字节数组硬匹配或工具类辅助</h3>
<p>不依赖第三方库时,直接用 <code>Arrays.equals()</code> 比较字节片段。例如判断是否为 PNG:</p>
<pre class="brush:java;toolbar:false;">private static boolean isPng(byte[] header) {
return header.length >= 8 &&
header[0] == (byte) 0x89 &&
header[1] == (byte) 0x4E &&
header[2] == (byte) 0x47 &&
header[3] == (byte) 0x0D &&
header[4] == (byte) 0x0A &&
header[5] == (byte) 0x1A &&
header[6] == (byte) 0x0A;
}
更简洁的方式是预定义常量数组:
private static final byte[] PNG_SIGNATURE = {(byte)0x89, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
// ...
if (header.length >= PNG_SIGNATURE.length &&
Arrays.equals(header, 0, PNG_SIGNATURE.length, PNG_SIGNATURE, 0, PNG_SIGNATURE.length)) {
return "image/png";
}
补充建议:处理边界与可靠性提升
仅靠文件头识别有局限,实际中可叠加简单策略提高鲁棒性:
- 对 MP4 类容器,先找
ftypbox,它可能不在绝对开头(存在自定义 box),此时需简单解析前若干个 box 头(每个 box 含 4 字节 size + 4 字节 type) - 若文件扩展名与头信息冲突(如 .jpg 文件实际是 PNG),优先信头部 —— 这正是校验的目的
- 对超大文件(如 10GB 视频),确保不触发 GC 压力:header 缓冲区始终是固定小数组(
- 如需更高精度(如区分 H.264/H.265 编码),需进一步解析 moov/trak box,但已超出「头部校验」范畴










