在Java中确定二进制/文本文件类型?

49

你想知道如何区分归档文件(jar/rar等)和文本文件(xml/txt,与编码无关)?


3
诡计问题 - 它们都是二进制文件。 - duffymo
12个回答

20

没有百分百准确的方法,以下是一些可能性:

  1. 查找文件头标识。不过,文件头标识是特定于文件类型的,所以你可能只能找到它是RAR文件,但不能得出更为普遍的答案,即它是文本文件还是二进制文件。

  2. 计算字符和非字符类型的数量。文本文件将主要包含字母字符,而二进制文件——尤其是像rar、zip等压缩文件——将倾向于均匀地表示字节。

  3. 查找定期重复的换行符模式。


14
使用Java 7的Files类探测文件类型
boolean isBinaryFile(File f) throws IOException {
        String type = Files.probeContentType(f.toPath());
        if (type == null) {
            //type couldn't be determined, assume binary
            return true;
        } else if (type.startsWith("text")) {
            return false;
        } else {
            //type isn't text
            return true;
        }
    }

11
那只是检查文件扩展名,而不是文件内容,因此无效。 - ares
2
根据文档,这取决于安装了什么。 - Adam
2
@ares 实际上它根本不仅仅检查文件扩展名。 - citizen conn
您可以删除文件扩展名并重试。 - justqb

13

这是我做的一个版本。相对简化一些,但对于拉丁字母的语言来说,应该可以很好地工作,只需要进行比例调整。

/**
 *  Guess whether given file is binary. Just checks for anything under 0x09.
 */
public static boolean isBinaryFile(File f) throws FileNotFoundException, IOException {
    FileInputStream in = new FileInputStream(f);
    int size = in.available();
    if(size > 1024) size = 1024;
    byte[] data = new byte[size];
    in.read(data);
    in.close();

    int ascii = 0;
    int other = 0;

    for(int i = 0; i < data.length; i++) {
        byte b = data[i];
        if( b < 0x09 ) return true;

        if( b == 0x09 || b == 0x0A || b == 0x0C || b == 0x0D ) ascii++;
        else if( b >= 0x20  &&  b <= 0x7E ) ascii++;
        else other++;
    }

    if( other == 0 ) return false;

    return 100 * other / (ascii + other) > 95;
}

1
感谢这个函数。我遇到的一个问题是,我不太清楚返回值是什么意思: return (ascii + other) * 100 / other > 95; 除非我漏掉了什么,否则它总是返回true:大多数情况下,大小将为1024,数据长度和(ascii + other)也将为1024。 因此,如果(ascii + other) * 100 == 102400, 那么102400 / other > 95=>102400 > 95 * other=>other < 1078 这意味着需要有超过1078(1024个之外)的“其他”才能返回false,显然是不可能的。 你的意思是吗?(other / size * 100 > 95) 还是我漏掉了什么? - Inversus
很酷。是的,我最终也选择了那个。再次感谢 :) - Inversus
2
对于UTF-16 LE,UTF-16 BE和带BOM的UTF-8失败。 - rince
@rince,那是可能的,随意编辑以增强BOM。不确定是否覆盖UTF 16会将其扩展为完全上下文字符解析器... - Ondra Žižka
你的算法将JavaScript文件检测为二进制文件。我认为更多的二进制比较可以解决这个问题。同时,@Michael von Wenckstern在那个时候为这个目的工作得更好。 - Ratata Tata
显示剩余2条评论

12

运行file -bi {filename}。如果它返回的内容以"text/"开头,那么这是非二进制文件,否则就是二进制文件。;-)


2
看起来还有“application/javascript”和“application/xml”。在这里查看http://en.wikipedia.org/wiki/Internet_media_type表明它并不那么简单。 - AaronJ
1
你可以使用 file -i {文件名} 命令检查,确保没有出现 charset=binary - Steinway Wu
我必须说,当我回答上面的问题时,并不完全是认真的。文本文件只是以特定方式解释的二进制文件。如果你指的是US-ASCII编码,那么你可以检查每个字节,看它是否符合你对文本的定义。但也许你指的是任何类型的字符编码。那将会更加困难。特别是如果你考虑到使用熵编码(经常出现的字符需要较少的位)的编码方式。另一方面,如果你指的是所有US-ASCII编码,那么一个Base64编码的图像也会被视为文本吗? - Wilfred Springer
从《猎鲨记》中:于是钟声手会大喊:“它们只是惯例符号!”而船员们则回答道。 - Wilfred Springer
在 macOS 上,我使用 file --help 命令发现应该使用 -bI 来输出 MIME 类型和字符集,而不是 -bi,后者只会输出“常规文件”。 - K. Symbol

9

请查看JMimeMagic库。

jMimeMagic是一个Java库,用于确定文件或流的MIME类型。


有趣的库,但这会有什么帮助呢?它可以告诉你MIME类型,但无法确定它是二进制还是文本。 - Adam
@Adam 我不明白你的问题?你可以从 MIME 类型本身推断文件是二进制还是非二进制的,对吧?也就是说,如果类型是 text/plain,它应该是一个文本文件。 - Daniel Hiller
1
好的,我说话直接了点。我的意思是你仍然需要编写额外的逻辑来解释MIME类型是二进制还是文本。'text/plain'并不是唯一的基于文本的MIME类型。 - Adam
1
@Adam 是的,我也这么认为,因为 application/json 也是一种文本表示形式。但是针对最初的问题非常不具体,我认为这应该足以作为一个起点 :) - Daniel Hiller

6

我使用了这段代码,对于英语和德语文本效果非常好:

private boolean isTextFile(String filePath) throws Exception {
    File f = new File(filePath);
    if(!f.exists())
        return false;
    FileInputStream in = new FileInputStream(f);
    int size = in.available();
    if(size > 1000)
        size = 1000;
    byte[] data = new byte[size];
    in.read(data);
    in.close();
    String s = new String(data, "ISO-8859-1");
    String s2 = s.replaceAll(
            "[a-zA-Z0-9ßöäü\\.\\*!\"§\\$\\%&/()=\\?@~'#:,;\\"+
            "+><\\|\\[\\]\\{\\}\\^°²³\\\\ \\n\\r\\t_\\-`´âêîô"+
            "ÂÊÔÎáéíóàèìòÁÉÍÓÀÈÌÒ©‰¢£¥€±¿»«¼½¾™ª]", "");
    // will delete all text signs

    double d = (double)(s.length() - s2.length()) / (double)(s.length());
    // percentage of text signs in the text
    return d > 0.95;
}

3
这个想法很有趣,但是我会选择使用 for 循环来计算文本和非文本字符,而不是使用 replaceAll 方法,因为后者会不必要地创建一个新字符串。将限制设置在 1000 个字符处意味着它不会过于昂贵,但这仍然是一种无用的代价。 - miniBill

4
如果文件由0x09(制表符)、0x0A(换行符)、0x0C(换页符)、0x0D(回车符)或0x20到0x7E的字节组成,则很可能是ASCII文本。
如果文件包含除上述三个以外的任何其他ASCII控制字符,即0x00到0x1F,则很可能是二进制数据。
UTF-8文本对于任何高位字节遵循非常特定的模式,但像ISO-8859-1这样的固定长度编码则不会。 UTF-16经常包含空字节(0x00),但只在每隔一个位置出现一次。
对于其他任何情况,您需要使用较弱的启发式方法。

检查是否存在0x10..0x1f的任何出现可能是检测二进制最简单的选项? - Stefan Haustein

3

提醒您,我选择了完全不同的方向。在我的情况下,只有两种类型的文件,任何给定文件是二进制文件的机会都很高。因此:

  1. 假设文件是二进制文件,尝试执行应该完成的操作(例如反序列化)
  2. 捕获异常
  3. 将文件视为文本文件
  4. 如果失败,则说明文件本身存在问题

我认为这是最好的方法。你真的在意文件类型吗?还是你关心你是否能够用它做某些事情。在许多情况下,如果你能够做到这些事情,你实际上并不需要知道类型是什么。 - stackexchanger

3
你可以尝试使用Apache Tika,我已经为这个功能开了一个专门的请求
但是目前来看,我认为这可能会起作用……需要进行更彻底的测试,可能还存在其他MIME类型库的问题,其中你仍然需要将类型映射到二进制还是非二进制。
var config = TikaConfig.getDefaultConfig();
var tika = new Tika( config );
var mimeTypes = config.getMimeRepository();

var mimetype = tika.detect(Path.of("my/foo"));
var rootType = mimeTypes.forName( mime ).getType().getType();
rootType.endsWith( "text" ); // text and x-text

你可能也想检查一下别名和父类型,这将有助于你发现 XML主要是基于文本的 或者 JavaScript是基于文本的 - Gagravarr

2

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