如何检查文件是否为二进制文件?

6
我写了以下方法,用于检查特定文件是否仅包含ASCII文本字符或除此之外还包含控制字符。您能否看一下这段代码,提出改进意见并指出疏漏?
逻辑如下:“如果文件的前500个字节包含5个或更多控制字符,则将其报告为二进制文件”。
谢谢。
public boolean isAsciiText(String fileName) throws IOException {

    InputStream in = new FileInputStream(fileName);
    byte[] bytes = new byte[500];

    in.read(bytes, 0, bytes.length);
    int x = 0;
    short bin = 0;

    for (byte thisByte : bytes) {
        char it = (char) thisByte;
        if (!Character.isWhitespace(it) && Character.isISOControl(it)) {

            bin++;
        }
        if (bin >= 5) {
            return false;
        }
        x++;
    }
    in.close();
    return true;
}
7个回答

3

x 看起来并没有什么作用。

如果文件小于500字节怎么办?

有些二进制文件存在这样一种情况:文件的前N个字节可能是头部,包含了一些对应用程序有用但是库不关心的数据。这个头部可能包含500多个ASCII字符,后面跟着的是接下来的几十亿字节的二进制数据。

如果无法打开或读取文件等,则应处理异常。


3

由于您将此类称为“isASCIIText”,因此您确切地知道自己正在寻找什么。换句话说,它不是“isTextInCurrentLocaleEncoding”。因此,您可以更准确地使用以下内容:

if (thisByte < 32 || thisByte > 127) bin++;
< p > < em >编辑,很久以后 - 评论中指出,这个简单的检查会被以许多换行符开头的文本文件所绊倒。最好使用“ok”字节的表格,并包括可打印字符(包括回车、换行和制表符,可能还包括换页符,尽管我认为现代文档很少使用这些字符),然后检查该表格。


这被标记为正确答案实在是个悲剧,因为这个算法会将包含“this\r\nis\r\nonly\r\ntext”的文件分类为二进制。 - Ingo
2
@Ingo 确实;最好检查一些控制字符和非控制字符的比率,还要检查文本中常见的控制字符等特殊情况。当我输入这个答案时,我还很年轻 :) - Pointy

3
  1. 如果文件大小小于500字节则会失败

  2. 代码行char it = (char) thisByte;在概念上存在问题,它混淆了字节和字符的概念,即隐含地假设编码是一个字节对应一个字符(从而排除了Unicode编码)。特别是,如果文件采用UTF-16编码,则该代码行会失败。

  3. 循环内部的返回语句(在我看来略有不妥)忘记关闭文件。


1
我注意到的第一件事——与您的实际问题无关,但您应该在finally块中关闭输入流以确保它总是被完成。通常这只是处理异常,但在你的情况下,当返回false时,你甚至不会关闭文件的流。
除此之外,为什么要与ISO控制字符进行比较?那不是一个“二进制”文件,而是一个“包含5个或多个控制字符的文件”。我认为更好的解决方法是,反转检查——编写一个isAsciiText函数,它断言文件中(或者如果你愿意,则是前500个字节)的所有字符都属于一组已知良好的字节。
从理论上讲,只检查文件的前几百个字节可能会给你带来麻烦,如果它是某种组合文件(例如,嵌入图片的文本),但在实践中,我怀疑每个这样的文件都将在开头具有二进制标头数据,所以你可能没问题。

0

这在Linux或Solaris的JDK安装包中是行不通的。它们有一个shell脚本启动,然后是一个二进制数据块。

为什么不使用像jMimeMagic(http://http://sourceforge.net/projects/jmimemagic/)这样的库来检查MIME类型,并根据MIME类型决定如何处理文件呢?


0

可以解析并与已知的二进制文件头字节列表进行比较,例如这里提供的(并备份这里)。

问题是,需要有一个排序过的仅包含二进制头的列表,而且该列表可能并不完整。例如,在某些Equinox框架jar文件中读取和解析二进制文件。但如果需要识别特定的文件类型,这应该是有效的。

如果你在Linux上,对于磁盘上的现有文件,使用本地file命令执行应该很好用:

String command = "file -i [ZIP FILE...]";
Process process = Runtime.getRuntime().exec(command);
...

它将输出有关文件的信息:
...: application/zip; charset=binary

你可以使用grep或者在Java中进一步过滤这些文件,具体取决于你是只需要对文件的二进制特征进行估计,还是需要找出它们的MIME类型。
如果要解析InputStreams,比如存档文件中嵌套的文件内容,很遗憾,除非使用仅限于shell的程序(比如unzip),否则无法实现,如果你想避免创建临时解压文件。
对于这个问题,我目前采用了检查前500个字节的粗略估计方法,就像上面的示例中所提到的那样;我使用了Character.isIdentifierIgnorable(codePoint)而不是Character.isWhitespace/isISOControl(char),假设使用了UTF-8默认编码。
private static boolean isBinaryFileHeader(byte[] headerBytes) {
    return new String(headerBytes).codePoints().filter(Character::isIdentifierIgnorable).count() >= 5;
}

public void printNestedZipContent(String zipPath) {
    try (ZipFile zipFile = new ZipFile(zipPath)) {
        int zipHeaderBytesLen = 500;
        zipFile.entries().asIterator().forEachRemaining( entry -> {
            String entryName = entry.getName();
            if (entry.isDirectory()) {
                System.out.println("FOLDER_NAME: " + entryName);
                return;
            }
            // Get content bytes from ZipFile for ZipEntry 
            try (InputStream zipEntryStream = new BufferedInputStream(zipFile.getInputStream(zipEntry))) {
                // read and store header bytes
                byte[] headerBytes = zipEntryStream.readNBytes(zipHeaderBytesLen);
                // Skip entry, if nested binary file
                if (isBinaryFileHeader(headerBytes)) {
                    return;
                }
                // Continue reading zipInputStream bytes, if non-binary
                byte[] zipContentBytes = zipEntryStream.readAllBytes();
                int zipContentBytesLen = zipContentBytes.length;
                // Join already read header bytes and rest of content bytes
                byte[] joinedZipEntryContent = Arrays.copyOf(zipContentBytes, zipContentBytesLen + zipHeaderBytesLen);
                System.arraycopy(headerBytes, 0, joinedZipEntryContent, zipContentBytesLen, zipHeaderBytesLen);
                // Output (default/UTF-8) encoded text file content
                System.out.println(new String(joinedZipEntryContent));
            } catch (IOException e) {
                System.out.println("ERROR getting ZipEntry content: " + entry.getName());
            }
        });
    } catch (IOException e) {
        System.out.println("ERROR opening ZipFile: " + zipPath);
        e.printStackTrace();
    }
}

-1
  1. 如果你忽略了read()函数的返回值,那么当文件长度小于500字节时会怎样?
  2. 当你返回false时,你没有关闭文件。
  3. 在将字节转换为字符时,你假设文件是7位ASCII码。

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