Java File.listFiles() 返回的文件根据 `exists()` 方法来判断可能是 '不存在' 的。

7
我注意到我们生产代码中存在这个问题:
java.lang.IllegalArgumentException: /somePath/�.png does not exist
    at org.apache.commons.io.FileUtils.sizeOf(FileUtils.java:2413)
    at org.apache.commons.io.FileUtils.sizeOfDirectory(FileUtils.java:2479)

根本原因是这个:
import java.io.File;

public class FileNameTest
{

    public static void main(String[] args)
    {
        File[] files = new File("/somePath").listFiles();
        for (File file : files)
        {
            System.out.println(file + " - " + (file.exists() ? "exists" : "missing!!"));
        }
    }

}

输出:

0.png - exists
7.png - exists
4.png - exists
8.png - exists
1.png - exists
3.png - exists
�.png - missing!!
2.png - exists
5.png - exists
�.png - missing!!
6.png - exists
d.png - exists
$.png - exists
s.png - exists
+.png - exists
9.png - exists

“缺失”的文件名为"µ" (Mu)"€" (Euro)
此外,这些文件名似乎使用了错误的编码方式。当我在bash中列出这些文件时,它们也显示不正确。当我将ls的输出从latin1转换为UTF-8时,它们会正确显示(至少是mu)。
但尽管如此…
  1. 这些文件确实存在
  2. file.listFiles()会将它们列出
  3. 对于这两个特殊情况:file.exists()返回false
我相信这是JVM中的一个bug。有人能确认这一点吗?
是否已经有了bug-report?有什么方法可以修复这个问题吗?(重命名文件不是一个选项,因为它们是由用户生成的,可能以任何形式重新出现。)
我的系统:
  • Ubuntu 4.2.0
  • Java版本 "1.8.0_102"
  • Java(TM) SE运行环境 (版本 1.8.0_102-b14)
  • Java HotSpot(TM) 64位服务器虚拟机 (版本 25.102-b14, 混合模式)
  • Apache Commons IO 2.4

文件系统有类似的东西吗? - Frederic Leitenberger
在Linux上,文件名是没有特定编码存储的。因此,您必须在某个地方定义文件名的编码方式。一种可能性是使用JVM中的file.encoding。您提到当将文件名转换为UTF-8时,文件名无法正确显示,因此您的示例将始终失败。在这个例子中,您必须选择适用于所有文件的正确编码方式。 - Sidias-Korrado
1
问号符号代表无法转换的字符,它将始终与实际文件名不同。 - Sidias-Korrado
1
这种行为并不是一个错误,而是由于文件系统内缺少文件名编码信息或Java选择使用的接口最兼容所导致的结果。 - Sidias-Korrado
1
因此,Java会将任何无法转换的字符转换为问号符号。因此,生成的文件名不再对应于硬盘上的实际文件名,因此无法找到... 叹气好的,谢谢! - Frederic Leitenberger
显示剩余2条评论
2个回答

6
这不是一个bug,而是由于文件系统中缺少编码信息所导致的后果。 Java无法正确表示文件名,因为它不知道编码。因此,在没有指定正确编码的情况下,该文件无法从Java中访问。
解决这个问题的最简单方法是正确设置file.encoding属性,并在所有文件名中使用该编码。
编辑:我发现一篇文章展示了另一种可能的行为,也许更改file.encoding并不能帮助。如果您想使用其他编码,请测试一下。http://jonisalonen.com/2012/java-and-file-names-with-invalid-characters/ 我还发现了一个可能相关的讨论:设置文件名编码

谢谢。我最终使用了 convmv -f LATIN1 -t UTF-8 -r . 命令来转换所有“损坏”的文件。问题现在已经解决了。 - Frederic Leitenberger
相关的是,Unix文件名虽然看起来像文本字符串,但实际上只是字节序列,其中三个字节值(NUL、/、.)具有特殊含义。 - Raedwald

0

没有解决方案,但可以如何检测那些没有使用File#exists的文件。

文件名源自于磁盘上错误编码(而不是UTF-8)的文件名。它会给出一个Unicode占位符U+FFFC ,而不是错误的字节。

你可能会认为没有人会用U+FFFC来命名他/她的文件名。所以:

String name = file.getName();
if (name.contains("\uFFFC")) {
    String namePattern := name.replace(".", "\\.").replace("\uFFFC", ".");
    ... file is problematic
}

在每种情况下,您可能需要使用模式重命名来更正调用ProcessBuilder的文件。

可能只是将目录作为使用CP1252编码(Windows Latin-1)的文件读取,可能会得到正确的文件名。

或者谁知道,新的文件和路径方法,如目录遍历,可能会显示一些改进。我不太确定。虽然字符集转换器可以提供有问题的字节。


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