java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java判断文件类型

Java实现判断文件类型的完整示例代码

作者:南城花随雪。

本文将介绍一个轻量级Java工具,通过魔数和扩展名双重校验,快速识别JPEG、PNG、PDF、ZIP等常见格式,无需第三方依赖,安全可靠,非常适合上传校验和批量扫描场景

一、项目背景详细介绍

在许多应用场景中,需根据文件内容或扩展名来判断文件的真实类型,而非单纯依据用户输入的后缀名,以防止:

常见做法包括读取文件魔数(Magic Number,又称文件头签名)或使用 JDK 内置 URLConnection.guessContentTypeFromStream、第三方库(如 Apache Tika)进行 MIME 类型探测。本项目旨在:

实现一个轻量、无外部依赖的工具,通过检查文件头字节(魔数)及扩展名双重校验,快速准确地判断常见文件类型。

二、项目需求详细介绍

1.支持文件类型

2.双重校验策略

3.易用 API

静态工具类 FileTypeDetector:

enum FileType { JPEG, PNG, GIF, BMP, WEBP, PDF, DOC, DOCX, …, UNKNOWN }
class DetectionResult {
    FileType type;
    Source source; // HEADER or EXTENSION
}
DetectionResult detect(File file);
Optional<FileType> detectByHeader(File file);
Optional<FileType> detectByExtension(String fileName);

支持 InputStream、byte[] 等多种形式。

4.性能与安全

5.配置可扩展

6.文档与示例

三、相关技术详细介绍

1.魔数(Magic Number)原理

2.扩展名判断

3.Apache Tika 对比

4.Java I/O 与 NIO

5.线程安全与缓存

四、实现思路详细介绍

实现思路

1.定义枚举与数据结构

2.FileTypeDetector 类设计

DetectionResult detect(File file):

Optional<FileType> detectByHeader(InputStream in):类似 detect(File) 但不关闭流。

Optional<FileType> detectByExtension(String name):根据小写后缀查表。

3.示例应用

4.可扩展注册

/*
 * =====================================================
 * File: FileTypeDetector.java
 * 工具类:基于魔数和扩展名检测文件类型
 * =====================================================
 */
package com.example.filetype;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public final class FileTypeDetector {
    public enum FileType {
        JPEG, PNG, GIF, BMP, WEBP,
        PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT,
        ZIP, RAR, _7Z, GZIP,
        MP3, MP4, AVI,
        UNKNOWN
    }

    public enum Source { HEADER, EXTENSION, NONE }

    public static class DetectionResult {
        private final FileType type;
        private final Source source;
        public DetectionResult(FileType type, Source source) {
            this.type = type; this.source = source;
        }
        public FileType getType() { return type; }
        public Source getSource() { return source; }
        @Override public String toString() {
            return type + " via " + source;
        }
    }

    // 魔数映射表(仅示例签名前缀)
    private static final Map<FileType, byte[]> MAGIC_NUMBERS = Map.ofEntries(
        Map.entry(FileType.JPEG, new byte[]{(byte)0xFF,(byte)0xD8,(byte)0xFF}),
        Map.entry(FileType.PNG,  new byte[]{(byte)0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A}),
        Map.entry(FileType.GIF,  "GIF8".getBytes()),
        Map.entry(FileType.BMP,  "BM".getBytes()),
        Map.entry(FileType.WEBP,"RIFF".getBytes()), // needs further check
        Map.entry(FileType.PDF,  "%PDF".getBytes()),
        Map.entry(FileType.ZIP,  new byte[]{0x50,0x4B,0x03,0x04}),
        Map.entry(FileType.RAR,  "Rar!".getBytes()),
        Map.entry(FileType._7Z,  new byte[]{0x37,0x7A,0xBC,0xAF,0x27,0x1C}),
        Map.entry(FileType.GZIP, new byte[]{(byte)0x1F,(byte)0x8B}),
        Map.entry(FileType.MP3,  new byte[]{(byte)0xFF,(byte)0xFB}),
        Map.entry(FileType.PDF,  new byte[]{0x25,0x50,0x44,0x46}),
        Map.entry(FileType.MP4,  new byte[]{0x00,0x00,0x00,0x18,0x66,0x74,0x79,0x70}),
        Map.entry(FileType.AVI,  "RIFF".getBytes()) // needs further check
    );

    // 扩展名映射
    private static final Map<String, FileType> EXTENSION_MAP;
    static {
        Map<String, FileType> m = new HashMap<>();
        m.put("jpg", FileType.JPEG); m.put("jpeg", FileType.JPEG);
        m.put("png", FileType.PNG);
        m.put("gif", FileType.GIF);
        m.put("bmp", FileType.BMP);
        m.put("webp", FileType.WEBP);
        m.put("pdf", FileType.PDF);
        m.put("doc", FileType.DOC); m.put("docx", FileType.DOCX);
        m.put("xls", FileType.XLS); m.put("xlsx", FileType.XLSX);
        m.put("ppt", FileType.PPT); m.put("pptx", FileType.PPTX);
        m.put("txt", FileType.TXT);
        m.put("zip", FileType.ZIP);
        m.put("rar", FileType.RAR);
        m.put("7z", FileType._7Z);
        m.put("gz", FileType.GZIP);
        m.put("mp3", FileType.MP3);
        m.put("mp4", FileType.MP4);
        m.put("avi", FileType.AVI);
        EXTENSION_MAP = Collections.unmodifiableMap(m);
    }

    private static final int MAX_HEADER_SIZE = 16;

    private FileTypeDetector() { /* no instantiation */ }

    public static DetectionResult detect(File file) {
        try (InputStream in = new FileInputStream(file)) {
            Optional<FileType> byHeader = detectByHeader(in);
            if (byHeader.isPresent()) {
                return new DetectionResult(byHeader.get(), Source.HEADER);
            }
        } catch (IOException ignored) {}
        Optional<FileType> byExt = detectByExtension(file.getName());
        return new DetectionResult(byExt.orElse(FileType.UNKNOWN),
                                   byExt.isPresent() ? Source.EXTENSION : Source.NONE);
    }

    public static Optional<FileType> detectByHeader(InputStream in) throws IOException {
        in.mark(MAX_HEADER_SIZE);
        byte[] header = new byte[MAX_HEADER_SIZE];
        int read = in.read(header);
        in.reset();
        if (read <= 0) return Optional.empty();
        for (var entry : MAGIC_NUMBERS.entrySet()) {
            byte[] magic = entry.getValue();
            if (read >= magic.length && startsWith(header, magic)) {
                return Optional.of(entry.getKey());
            }
        }
        return Optional.empty();
    }

    private static boolean startsWith(byte[] data, byte[] prefix) {
        for (int i = 0; i < prefix.length; i++) {
            if (data[i] != prefix[i]) return false;
        }
        return true;
    }

    public static Optional<FileType> detectByExtension(String name) {
        if (name == null) return Optional.empty();
        int idx = name.lastIndexOf('.');
        if (idx < 0 || idx == name.length() - 1) return Optional.empty();
        String ext = name.substring(idx + 1).toLowerCase(Locale.ROOT);
        return Optional.ofNullable(EXTENSION_MAP.get(ext));
    }
}
/*
 * =====================================================
 * File: FileTypeDetectorTest.java
 * 单元测试:验证文件类型检测功能
 * =====================================================
 */
package com.example.filetype;

import org.junit.jupiter.api.Test;
import java.io.File;
import static org.junit.jupiter.api.Assertions.*;

class FileTypeDetectorTest {

    @Test
    void testDetectByExtension() {
        assertEquals(FileTypeDetector.FileType.PNG,
            FileTypeDetector.detectByExtension("image.PNG").get());
        assertTrue(FileTypeDetector.detectByExtension("unknown.bin").isEmpty());
    }

    @Test
    void testDetectByHeader() throws Exception {
        File png = new File("src/test/resources/sample.png");
        var res = FileTypeDetector.detectByHeader(new java.io.FileInputStream(png));
        assertEquals(FileTypeDetector.FileType.PNG, res.get());
    }

    @Test
    void testDetectFile() {
        File jpg = new File("src/test/resources/sample.jpg");
        var dr = FileTypeDetector.detect(jpg);
        assertEquals(FileTypeDetector.FileType.JPEG, dr.getType());
        assertEquals(FileTypeDetector.Source.HEADER, dr.getSource());
    }

    @Test
    void testUnknown() {
        File txt = new File("src/test/resources/readme.txt");
        var dr = FileTypeDetector.detect(txt);
        assertEquals(FileTypeDetector.FileType.TXT, dr.getType());
        assertEquals(FileTypeDetector.Source.EXTENSION, dr.getSource());
    }
}

代码详细解读

1.魔数检测

2.扩展名检测

3.综合检测

detect(File) 先尝试魔数检测,若成功返回 HEADER;否则按扩展名检测,未匹配则返回 UNKNOWN。

4.线程安全与性能

项目详细总结

本项目提供了轻量级的文件类型检测工具,优势在于:

项目常见问题及解答

WEBP、AVI 因同 RIFF 前缀如何区分?

魔数表可进一步检查后续字节(如 WEBP 在字节 8–12 区段);本示例简化处理。

扩展名不可信,是否可只用魔数?

可以仅用魔数,但某些类型魔数相同或混合写法需补充更详签名。

如何处理流式数据?

detectByHeader(InputStream) 支持对任意流检测,需确保流支持 mark/reset 或使用 PushbackInputStream。

性能瓶颈在何处?

I/O 读头与循环表匹配,可将魔数表按长度分组加速匹配或引入 Trie 结构。

如何添加新文件类型?

在 MAGIC_NUMBERS 和 EXTENSION_MAP 注册新签名和后缀即可。

扩展方向与性能优化

精细魔数:对 RIFF 类型文件,继续读取并校验字节 8–12 是否为 WEBP/AVI。

高效匹配结构:使用前缀树(Trie)存储魔数,以 O(n) 方式匹配多重签名。

异步批量扫描:使用 CompletableFuture 或并行流对目录进行并发检测,加快大规模文件分析。

缓存检测结果:对已检测文件路径/内容哈希进行 LRU 缓存,避免重复读取。

集成第三方库:结合 Apache Tika,提供更全面的 MIME 类型检测和元数据提取。

以上就是Java实现判断文件类型的完整示例代码的详细内容,更多关于Java判断文件类型的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文