如何获取文件的媒体类型(MIME类型)?

383

如何使用Java从文件获取媒体类型(MIME类型)? 我已经尝试了JMimeMagic和Mime-Util。第一个给了我内存异常,第二个没有正确关闭其流。

你将如何探测文件以确定其实际类型(不仅基于扩展名)?


5
提供了一个关于可用库的良好概述,网址为http://www.rgagnon.com/javadetails/java-0487.html。 - koppor
我使用了在这里发布的答案中提到的类:https://dev59.com/HW855IYBdhLWcg3wbjvO#10140531 - Joshua Pinter
4
现在Tika应该是答案了。下面的其他答案忽略了Tika的许多依赖关系,但我在tika-core中没有看到任何依赖项。 - javamonkey79
@javamonkey79 当我们使用TIka时,它会将文件转换为不再可用的格式。String contentType = tika.detect(is)。 - Cool Techie
28个回答

351

74
请注意,Files.probeContentType(Path)在多个操作系统上存在缺陷,并且已经有许多错误报告。我曾经遇到一个问题,在ubuntu上可行的软件在windows上会失败。似乎在windows上,Files.probeContentType(Path)总是返回null。这不是我的系统,所以我没有检查JRE或Windows版本。可能是Windows 7或8,使用的是Oracle JRE for Java 7。 - Silver
21
我正在运行OS X 10.9,但我获取".xml"、".png"和".xhtml"文件时返回了"null"。我不知道是不是我的操作出了什么问题,但这似乎相当糟糕。 - user372743
46
这种方法的一个主要限制是文件必须存在于文件系统中。这对于流或字节数组等情况无效。 - Necreaux
4
如果我从文件名中删除扩展名,这种方法将无法返回MIME类型。例如,如果文件名为“test.mp4”,我将其更改为“test”,该方法将返回null。同时,如果我将电影扩展名更改为png等,它将返回png MIME类型。请问您需要翻译其他内容吗? - Sarkhan
13
如果文件缺失或扩展名错误,这将毫无用处。 - shmosel
显示剩余12条评论

239

很遗憾,

mimeType = file.toURL().openConnection().getContentType();

这种方式并不可行,因为使用URL会锁定文件,导致该文件无法被删除。

不过,您可以尝试以下方法:

mimeType= URLConnection.guessContentTypeFromName(file.getName());

而且以下方式更为优越,它不仅使用文件扩展名,还会查看内容

InputStream is = new BufferedInputStream(new FileInputStream(file));
mimeType = URLConnection.guessContentTypeFromStream(is);
 //...close stream

然而,正如上面的评论所建议的那样,内置的MIME类型表相当有限,例如不包括MSWord和PDF。因此,如果你想要泛化,你需要超越内置的库,例如使用Mime-Util(这是一个很棒的库,同时使用文件扩展名和内容)。


8
完美的解决方案-对我帮助很大!将FileInputStream包装成BufferedInputStream是至关重要的一步-否则,guessContentTypeFromStream方法会返回null(所传递的InputStream实例应支持标记)。 - Yuriy Nakonechnyy
16
然而,URLConnection 识别的内容类型非常有限。例如,它无法检测 application/pdf - kpentchev
3
由于你没有关闭连接,导致它处于锁定状态。断开URLConnection的连接即可解锁。 - user207421
1
无论是guessContentTypeFromStream还是guessContentTypeFromName都无法识别例如mp4的文件类型。 - Hartmut Pfarr
3
guessContentTypeFromName()дҪҝз”Ёй»ҳи®Өзҡ„$JAVA_HOME/lib/content-types.propertiesж–Ү件гҖӮжӮЁеҸҜд»ҘйҖҡиҝҮжӣҙж”№зі»з»ҹеұһжҖ§System.setProperty("content.types.user.table","/lib/path/to/your/property/file");жқҘж·»еҠ иҮӘе·ұзҡ„жү©еұ•ж–Ү件гҖӮ - Govinnage Rasika Perera
显示剩余4条评论

67

使用 Apache Tika,您仅需要三行代码

File file = new File("/path/to/file");
Tika tika = new Tika();
System.out.println(tika.detect(file));

如果您拥有Groovy控制台,只需粘贴并运行此代码即可进行操作:

@Grab('org.apache.tika:tika-core:1.14')
import org.apache.tika.Tika;

def tika = new Tika()
def file = new File("/path/to/file")
println tika.detect(file)

请记住,Tika的API非常丰富,它可以解析“任何东西”。截至tika-core 1.14版本,您可以使用以下功能:


String  detect(byte[] prefix)
String  detect(byte[] prefix, String name)
String  detect(File file)
String  detect(InputStream stream)
String  detect(InputStream stream, Metadata metadata)
String  detect(InputStream stream, String name)
String  detect(Path path)
String  detect(String name)
String  detect(URL url)

查看apidocs获取更多信息。


2
Tika的一个不好的地方是,有很多依赖膨胀。它使我的jar包大小增加了54MB!!! - helmy
2
@helmyTika 1.17是独立的,只有648 KB大小。 - Sainan
对于基于文件扩展名的检测,可以使用new Tika().detect(file.toPath()),而不是基于文件内容的检测。 - Ilya Serbis
@Lu55的文档说仍然使用文档内容。我想你的意思是new Tika().detect(file.getPath()),它只使用文件扩展名。 - delucasvb

54

JAF API是JDK 6的一部分。请查看javax.activation包。

最有趣的类是javax.activation.MimeType - 实际的MIME类型持有者,以及javax.activation.MimetypesFileTypeMap - 其实例可以为文件解析MIME类型为字符串:

String fileName = "/path/to/file";
MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();

// only by file name
String mimeType = mimeTypesMap.getContentType(fileName);

// or by actual File instance
File file = new File(fileName);
mimeType = mimeTypesMap.getContentType(file);

4
不幸的是,getContentType(File) 的 Javadoc 表明:返回文件对象的 MIME 类型。该类中的实现调用 getContentType(f.getName()) - Matyas
3
请记住,您可以通过META-INF/mime.types文件扩展此功能,因此如果您被迫使用Java 6,则非常完美。 http://docs.oracle.com/javaee/5/api/javax/activation/MimetypesFileTypeMap.html - Chexpir
9
你可以通过使用MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(file)来避免创建一个新的对象。 - akostadinov
但它仍然只基于文件名返回内容类型。对于用户上传的文件来说,这尤其危险。 - Sergey Ponomarev
1
这不起作用,例如对于PDF文件(返回application/octet-stream)。 - Dmitriy Popov

40

Apache Tika提供了基于流前缀魔数的MIME类型检测,tika-core在此方面有所表现。 tika-core不会获取其他依赖项,这使得它像当前未维护的Mime Type Detection Utility一样轻巧。

以下是一个简单的Java 7代码示例,使用变量theInputStreamtheFileName

try (InputStream is = theInputStream;
        BufferedInputStream bis = new BufferedInputStream(is);) {
    AutoDetectParser parser = new AutoDetectParser();
    Detector detector = parser.getDetector();
    Metadata md = new Metadata();
    md.add(Metadata.RESOURCE_NAME_KEY, theFileName);
    MediaType mediaType = detector.detect(bis, md);
    return mediaType.toString();
}
请注意,MediaType.detect(...) 不能直接使用(TIKA-1120)。更多提示请参考https://tika.apache.org/1.24/detection.html

1
+1 另外,如果您没有任何资源名称或无法依赖原始名称,则可以省略 Metadata.RESOURCE_NAME_KEY。但在这种情况下,在某些情况下(例如办公文档),您将获得错误的结果。 - user1516873
1
如果文件名中没有扩展名,它在检测XLSX时会出现一些问题...但这个解决方案既简单又优雅。 - Oscar Pérez

26

If you're an Android developer, you can use a utility class android.webkit.MimeTypeMap which maps MIME-types to file extensions and vice versa.

Following code snippet may help you.

private static String getMimeType(String fileUrl) {
    String extension = MimeTypeMap.getFileExtensionFromUrl(fileUrl);
    return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}


3
如果使用本地文件路径(例如“/sdcard/path/to/video.extension”)进行尝试,这也是有效的。问题在于,如果本地文件路径中包含空格,则始终返回 null。 - nmxprime

19

来自 roseindia:

FileNameMap fileNameMap = URLConnection.getFileNameMap();
String mimeType = fileNameMap.getContentTypeFor("alert.gif");

7
谁给这个回答点了踩,请留下评论,这样我(和其他人)就可以学习如何发布更好的回答。 - AlikElzin-kilaka
3
我没有给你投反对票,但是getFileNameMap对于许多基本文件类型例如“bmp”都不起作用。另外,URLConnection.guessContentTypeFromName返回相同的结果。 - Ovidiu Buligan
5
函数不完整。从Java 7开始,html、pdf和jpeg扩展名将返回正确的MIME类型,但js和css却返回null! - djsumdog
我用“webm”进行了测试,结果返回了null。 - Henrique Rocha
澄清一下,Files.probeContentType(Path.of("my-file.css")) 是一种更好的处理方式(我在java11中进行了测试),因为它支持更多的文件类型。 - undefined

19
我只是想知道大多数人如何从Java文件中提取mime类型?
我发布了我的SimpleMagic Java包,它允许从文件和字节数组中确定内容类型(mime-type)。它旨在读取并运行Unix file(1)命令魔术文件,这是大多数Unix操作系统配置的一部分。
我尝试过Apache Tika,但它非常庞大,有大量依赖项,URLConnection不使用文件的字节,而MimetypesFileTypeMap也只查看文件名。
使用SimpleMagic,您可以执行以下操作:
// create a magic utility using the internal magic file
ContentInfoUtil util = new ContentInfoUtil();
// if you want to use a different config file(s), you can load them by hand:
// ContentInfoUtil util = new ContentInfoUtil("/etc/magic");
...
ContentInfo info = util.findMatch("/tmp/upload.tmp");
// or
ContentInfo info = util.findMatch(inputStream);
// or
ContentInfo info = util.findMatch(contentByteArray);

// null if no match
if (info != null) {
   String mimeType = info.getMimeType();
}

2
已在多个图像文件上进行了测试,所有文件的扩展名都已更改。您的出色库处理得很好。当然,它也非常轻巧 :). - saurabheights
1
是的,这个方法很有效。对于那些需要在Android中使用此解决方案的人,您只需在build.gradle文件中包含以下内容:compile('com.j256.simplemagic:simplemagic:1.10') - jkincali
该库适用于所有文件。比其他所有库都更好,因为它适用于诸如PDF、XLS、XLSX、DOC和DOCX之类的文档。它不能正常地处理XLS,但您可以通过ContentInfo的其他方法(如getMessage())来检查它。 - keivan shirkoubian
你能否向@keivanshirkoubian提交一个问题,附上一个未正确完成的xls样本?https://github.com/j256/simplemagic/issues - Gray
好的,@Gray,我有时间时会翻译它。 - keivan shirkoubian
1
@Gray 我已经在您的存储库中提交了有关旧Excel文件的问题。 [问题链接](https://github.com/j256/simplemagic/issues/85) - keivan shirkoubian

17

如果你被卡在Java 5-6上,那么可以使用这个来自servoy开源产品的实用类:MimeTypes.java

你只需要这个函数。

public static String getContentType(byte[] data, String name)

它探查内容的前几个字节并根据这些内容而不是文件扩展名返回内容类型。


7

为了贡献我的意见:

简短概括

我使用 MimetypesFileTypeMap 并将任何不在其中的 MIME 类型,特别是我需要的,添加到 mime.types 文件中。

现在,详细阅读:

首先,MIME 类型列表是巨大的,请参见此处: https://www.iana.org/assignments/media-types/media-types.xhtml

我喜欢首先使用 JDK 提供的标准设施,如果不行,我会去寻找其他方法。

通过文件扩展名确定文件类型

自从 1.6 版本以来,Java 就有了 MimetypesFileTypeMap,正如上面的一个答案所指出的那样,它是确定 MIME 类型最简单的方法:

new MimetypesFileTypeMap().getContentType( fileName );

在其基本实现中,它并没有做太多事情(即它适用于.html文件,但不适用于.png文件)。但是,您可以非常简单地添加任何所需的内容类型:
  1. 在项目的META-INF文件夹中创建名为“mime.types”的文件
  2. 为每个需要的mime类型添加一行,而默认实现不提供这些类型(有数百种mime类型,并且随着时间的推移列表会增长)。
例如,png和js文件的示例条目如下:
image/png png PNG
application/javascript js

关于mime.types文件格式,请查看此处的更多细节: https://docs.oracle.com/javase/7/docs/api/javax/activation/MimetypesFileTypeMap.html

从文件内容确定文件类型

自1.7以来,Java拥有java.nio.file.spi.FileTypeDetector,它定义了一种标准API,以实现特定方式确定文件类型。

要获取文件的MIME类型,您只需使用Files并在代码中执行以下操作:

Files.probeContentType(Paths.get("either file name or full path goes here"));

API定义提供了支持从文件名或文件内容(魔术字节)确定文件MIME类型的功能。这就是为什么probeContentType()方法会抛出IOException的原因,因为实现此API的某些情况下使用Path来实际尝试打开与其关联的文件。
再次强调,这个(JDK自带的)基本实现还有很多需要改进的地方。
在一个遥远的理想世界里,所有试图解决这个文件到MIME类型问题的库都会简单地实现java.nio.file.spi.FileTypeDetector,你只需将首选的实现库的jar文件放入类路径中即可。
但在现实世界中,你需要TL,DR部分,你应该找到名称旁边星星最多的库并使用它。对于这种特殊情况,我暂时不需要一个(尚未需要;))。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接