Java 无法打开文件名中包含代理 Unicode 值的文件?

IT小君   2021-11-29T22:45:36

我正在处理对文件执行各种 IO 操作的代码,我想让它能够处理国际文件名。我正在使用 Java 1.5 的 Mac 上工作,如果文件名包含需要代理的 Unicode 字符,则 JVM 似乎无法找到该文件。例如,我的测试文件是:

"草鷗外.gif" 它被分解成 Java 字符 \u8349\uD85B\uDFF6\u9DD7\u5916.gif

如果我从此文件名创建文件,则无法打开它,因为出现 FileNotFound 异常。即使在包含文件的文件夹上使用它也会失败:

File[] files = folder.listFiles(); 
for (File file : files) {
    if (!file.exists()) {
        System.out.println("Failed to find File"); //Fails on the surrogate filename
    }
}

我实际处理的大部分代码都是以下形式:

FileInputStream instream = new FileInputStream(new File("草鷗外.gif"));
// operations follow

有什么方法可以解决这个问题,要么转义文件名,要么以不同方式打开文件?

评论(4)
IT小君

我怀疑 Java 或 Mac 之一正在使用CESU-8而不是正确的 UTF-8。Java 使用“修改后的 UTF-8”(这是 CESU-8 的一个轻微变体)用于各种内部目的,但我不知道它可以将它用作文件系统/defaultCharset。不幸的是,我这里既没有 Mac 也没有 Java 可以测试。

“Modified”是“badly bugged”的一种修改方式。而不是为像𦿶这样的补充(非 BMP)字符输出一个四字节的 UTF-8 序列:

\xF0\xA6\xBF\xB6

它为每个代理输出一个 UTF-8 编码的序列:

\xED\xA1\x9B\xED\xBF\xB6

这不是有效的 UTF-8 序列,但无论如何很多解码器都会允许它。问题是,如果您通过真正的 UTF-8 编码器来回传输,您会得到一个不同的字符串,即上面的四字节字符串。尝试访问具有该名称的文件并繁荣!失败。

因此,首先让我们使用一个使用字节作为文件名的平台(例如 Python 2.x)检查文件名在当前文件系统下的实际存储方式:

$ python
Python 2.x.something (blah blah)
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.listdir('.')

在我的文件系统(Linux、ext4、UTF-8)上,文件名“草𦿶鸥外.gif”显示为:

['\xe8\x8d\x89\xf0\xa6\xbf\xb6\xe9\xb7\x97\xe5\xa4\x96.gif']

这就是你想要的。如果这就是你得到的,那很可能是 Java 做错了。如果您获得更长的六字节字符版本:

['\xe8\x8d\x89\xed\xa1\x9b\xed\xbf\xb6\xe9\xb7\x97\xe5\xa4\x96.gif']

这可能是 OS X 做错了……它总是存储这样的文件名吗?(或者文件最初来自其他地方吗?)如果您将文件重命名为“正确”版本怎么办?:

os.rename('\xe8\x8d\x89\xed\xa1\x9b\xed\xbf\xb6\xe9\xb7\x97\xe5\xa4\x96.gif', '\xe8\x8d\x89\xf0\xa6\xbf\xb6\xe9\xb7\x97\xe5\xa4\x96.gif')
2021-11-29T22:45:37   回复
IT小君

如果您环境的默认语言环境不包含这些字符,您将无法打开该文件。

请参阅:File.exists() 失败,名称中包含 unicode 字符

编辑: 好的..您需要更改系统区域设置。无论您使用什么操作系统。

编辑

请参阅:如何在 Java 中打开包含重音符号的文件?

请参阅:Mac 上的 JFileChooser 看不到以中文字符命名的文件?

2021-11-29T22:45:37   回复
IT小君

结果证明这是 Mac JVM 的问题(在 1.5 和 1.6 上测试)。不能使用 Java File 类访问包含补充字符/代理对的文件名。我最终编写了一个 JNI 库,其中包含针对 Mac 版本项目 (ick) 的 Carbon 调用。我怀疑 bobince 提到的 CESU-8 问题,因为获取 UTF-8 字符的 JNI 调用返回了 CESU-8 字符串。看起来这不是你真正可以解决的问题。

2021-11-29T22:45:37   回复
IT小君

这是 old-skool java File api 中的一个错误,也许只是在 mac 上?无论如何,新的 java.nio api 工作得更好。我有几个包含 unicode 字符和内容的文件,这些文件无法使用 java.io.File 和相关类加载。将我所有的代码转换为使用java.nio.Path 之后,一切都开始工作了。我用java.nio.Files替换了 org.apache.commons.io.FileUtils(有同样的问题)......

...并确保使用适当的字符集读取和写入文件的内容,例如: Files.readAllLines(myPath, StandardCharsets.UTF_8)

2021-11-29T22:45:38   回复