从扩展名确定文件类型

Determining File Type from Extension

Java 中是否有一种简单的方法可以将文件扩展名转换为特定文件类型?也就是说,我想将“.doc”翻译成“Microsoft Word 文档”。但我也不想查看每个文件内部以确定 MIME 类型等,因为这会影响性能。

是否有库或数据库文件列出所有当前接受的扩展名及其含义?我可以以编程方式加载某些内容,然后在需要时进行搜索吗?

Microsoft has a support article 常见文件扩展名。

list of file formats”上的维基百科页面似乎非常详尽。

应该很容易复制/粘贴其中一个(或两个)列表,使用文本编辑器调整格式,并通过硬编码数组或外部资源文件将其推送到您的代码中。

请记住,如果您走这条路,那么您就相信这些文件包含扩展程序声明的数据类型。这将是一个有点脆弱的解决方案,因为它只需要一个上游错误就可以解决问题。

IMO 更好的方法是从文件本身的前几个字节中读取 magic numbers(即文件签名)。几乎任何生产或商业软件都会这样做(至少)而不是信任文件扩展名。恶意用户仍有可能伪造签名,但这需要有意的操作,而不仅仅是编码错误。

这样做的成本与检查扩展的成本确实没有太大区别;除非你正在处理大量的文件或者有一些非常紧迫的截止日期(在这种情况下,Java 可能不是最好的选择),这两种方法都不需要太多要求 OS 阅读来自磁盘的少数字节。检查幻数只需要读取更多的字节,以及每个文件上 opening/closing 流的开销。

据我所知,这些名称只能在注册表中找到。我见过尝试使用 Swing 的 FileView 来获取它们,但我不会依赖它,尤其是对于无头代码。

public static String getFullFileTypeName(String extension)
throws IOException,
       InterruptedException {

    if (!extension.startsWith(".")) {
        extension = "." + extension;
    }

    String progID = readDefaultValue(extension);
    String name = readDefaultValue(progID);

    return name;
}

private static String readDefaultValue(String node)
throws IOException,
       InterruptedException {

    String registryPath = "HKEY_CLASSES_ROOT\" + node;
    ProcessBuilder builder = new ProcessBuilder(
        "reg.exe", "query", registryPath, "/ve");
    builder.redirectError(ProcessBuilder.Redirect.INHERIT);

    String value = null;
    Process process = builder.start();
    try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(process.getInputStream()))) {

        String line;
        while ((line = reader.readLine()) != null) {
            int regSZIndex = line.indexOf("REG_SZ");
            if (regSZIndex >= 0) {
                value = line.substring(regSZIndex + 6).trim();
                break;
            }
        }

        // Consume remaining output.
        if (line != null) {
            int c;
            do {
                c = reader.read();
            } while (c >= 0);
            // As of Java 11, the above loop can be replaced with:
            //reader.transferTo(Writer.nullWriter());
        }
    }

    int returnCode = process.waitFor();
    if (returnCode != 0) {
        throw new IOException("Got return code " + returnCode
            + " from " + builder.command());
    }

    if (value == null) {
        throw new IOException(
            "Could not find value for \"" + registryPath + "\"");
    }

    return value;
}