在使用来自Oracle的Java 7时,Mac OS X上的File.list()函数无法正确检索带有非ASCII字符的文件名。

18

我使用Java 7 from Oracle在Mac OS X上使用File.list()获取文件名为非ASCII字符的文件时遇到了问题。

以下是我的示例:

import java.io.*;
import java.util.*;

public class ListFiles {

  public static void main(String[] args) 
  {
    try { 
      File folder = new File(".");
      String[] listOfFiles = folder.list(); 
      for (int i = 0; i < listOfFiles.length; i++) 
      {
        System.out.println(listOfFiles[i]);
      }
      Map<String, String> env = System.getenv();
      for (String envName : env.keySet()) {
        System.out.format("%s=%s%n",
            envName,
            env.get(envName));
      }
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
  }

}

使用苹果的Java 6 运行此示例,一切正常。
....
Folder-ÄÖÜäöüß
吃饭.txt
....

使用Oracle Java 7运行此示例,结果如下:

....
Folder-A��O��U��a��o��u����
������.txt
....

但是,如果我设置环境如下(在以上两种情况中未设置):

LANG=en_US.UTF-8

使用来自Oracle的Java 7得到了期望的结果:

....
Folder-ÄÖÜäöüß
吃饭.txt
....

我的问题在于我不想设置LANG环境变量。这是一个GUI应用程序,我希望将其部署为Mac OS X应用程序,这样做,LSEnvironment设置会被忽略。
<key>LSEnvironment</key>
<dict>
  <key>LANG</key>
  <string>en_US.UTF-8</string>
</dict>

在 Info.plist 中的 LSEnvironment 不起作用(也可以参见 此处)。

我该怎么做才能从 Oracle 的 Java 7 中正确检索 Mac OS X 上的文件名,而无需设置 LANG 环境变量?在 Windows 和 Linux 中,不存在这个问题。

编辑:

如果我使用以下方法打印单个字节:

byte[] x = listOfFiles[i].getBytes();
for (int j = 0; j < x.length; j++) 
{
    System.out.format("%02X",x[j]);
    System.out.print(" ");
}
System.out.println();

正确的结果是:

Folder-ÄÖÜäöüß
46 6F 6C 64 65 72 2D 41 CC 88 4F CC 88 55 CC 88 61 CC 88 6F CC 
88 75 CC 88 C3 9F 
吃饭.txt
E5 90 83 E9 A5 AD 2E 74 78 74 

错误的结果如下:

Folder-A��O��U��a��o��u����
46 6F 6C 64 65 72 2D 41 EF BF BD EF BF BD 4F EF BF BD EF BF BD 
55 EF BF BD EF BF BD 61 EF BF BD EF BF BD 6F EF BF BD EF BF BD 
75 EF BF BD EF BF BD EF BF BD EF BF BD  
������.txt
EF BF BD EF BF BD EF BF BD EF BF BD EF BF BD EF BF BD 2E 74 78 74 

因此我们可以看到,如果未设置LANG(仅适用于来自Oracle的Java 7版本),Files.list()将使用UTF-8 "EF BF BD" = Unicode U+FFFD = 替换字符替换一些字节。

1
有趣的问题,加1。你查过错误数据库了吗? - Andrew Thompson
2
是的,我已经找到http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4733494。该漏洞报告的结论是:关闭,不是缺陷。有趣的是,除了OS X以外的其他平台上的苹果Java和Oracle的Java都没有这种行为。 - user1761231
1
我刚刚测试了一下,结果出现了相反的问题:来自苹果的Java 6u35无法使用正确的编码,而来自Oracle的Java 7u7可以。你的区域设置是什么?在终端中运行“locale”命令;我得到的是“CTYPE”设置为“UTF-8”,其他所有设置都为“C”。 “LANG”和“LC_ALL”未设置。 - Joni
如果我在终端中运行此程序,所有情况下都没问题,因为LANG始终设置为en_US.UTF-8。问题是当作为APP捆绑包运行Java程序时,LANG未设置,并且据我所知无法设置LANG(请参见我的原始帖子末尾)。 - user1761231
为什么终端中设置了LANG?你修改了.bashrc或类似的东西吗? - Joni
1
这个问题在Java 7u40中已经被Oracle最终解决。请参见http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8003228。 - user1761231
5个回答

4
如果其他方法都失败了,创建一个包装JVM的程序,设置LC_CTYPE环境变量,然后启动你的应用。OS X不在意plist告诉它运行哪个程序,对吧?最简单的方法可能是用shell脚本创建这个包装器:
#!/bin/bash
export LC_CTYPE="UTF-8" # Try other options if this doesn't work
exec java your.program.Here

问题出在Java从文件系统中读取文件名的方式上,无论是来自苹果还是Oracle的任何版本Java都存在这个问题。文件系统中的文件名实际上是二进制数据,必须解码才能将其作为Java中的字符串使用。(您可以在我的博客中阅读更多有关此问题的信息。)
编码检测因平台和版本而异,因此这可能是Apple Java 6和Oracle Java 7之间不同的地方:Java 6正确地检测到系统设置为UTF-8,而Java 7则错误。
但奇怪的是,当我尝试使用以下程序复现此问题时,我发现Java 6和Java 7都正确地使用UTF-8解码文件名(它们在终端上正确打印)。对于其他I/O,Java 6u35使用MacRoman作为默认字符集,而Java 7u7使用UTF-8(由file.encoding系统属性显示)。
import java.io.*;

public class Test {
  public static void main(String[] args) {
    System.setOut(new PrintStream(System.out, true, "UTF-8"));
    System.out.println(System.getProperty("file.encoding"));
    for (File f: new File(".").listFiles) {
      System.out.println(g.getName());
    }
  }
}

当我在OS 10.7上运行locale时,会得到以下输出。似乎在我的系统上,Java 6不能正确解释LC_CTYPE的值。据我所知,该系统没有进行任何自定义设置,一切都是英文,因此这应该是默认配置:
LANG=
LC_COLLATE="C"
LC_CTYPE="UTF-8"
LC_MESSAGES="C"
LC_MONETARY="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_ALL=

如果您尝试重现我的示例,请删除所有的LANG和LC_xxx环境变量(当您启动OS X应用程序包时出现的情况)。如果您在终端中使用LANG=en_US.UTF-8或LANG=de_DE.UTF-8运行它,我的示例将在Apple的Java或Oracle的Java上正确运行。 - user1761231
我创建了一个应用程序包装器,调用一个设置环境变量的bash脚本,最后调用作为资源包含的原始应用程序。它运行良好。谢谢。 - user1761231

2

由于在Java6中运行可以得到正确的结果,那么这样做是否可行:

System.out.println(new String(listOfFiles[i].getBytes(),"UTF-8"));

解决问题吗?

此建议的构造函数 明确将listOfFiles [i]字符串解释为UTF-8编码的字符串。

编辑:

因为它无法工作,这意味着UTF-8不是os x的默认编码。维基百科说Mac OS Roman 是默认编码。所以我建议尝试:

System.out.println(new String(listOfFiles[i].getBytes(),"MacRoman"));

但这应该是与相同的

System.out.println(new String(listOfFiles[i].getBytes()));

所以如果这也不起作用,那就说明可能存在一个 bug,正如 Andrew Thomson 在你的问题评论中所述。

@Andrew 谢谢你的建议 :) 我对答案的一般标准是它必须包含至少一些研究,并且我在发布答案时总是在我的机器上运行代码。我同意我的初始回答形式更像是一条评论,但我从未打算一开始就这样留下它。 - linski
1
@linski 很棒。在阅读了新的编辑后,我决定点赞。但要绝对正确,我是猜测了这个错误,而原帖作者找到了它并发布了链接(+1 给他们)。 - Andrew Thompson
System.out.println(new String(listOfFiles[i].getBytes(),"MacRoman")); 的结果是 Folder-AÔøΩÔøΩOÔøΩÔøΩUÔøΩÔøΩaÔøΩÔøΩoÔøΩÔøΩuÔøΩÔøΩÔøΩÔøΩ ...System.out.println(new String(listOfFiles[i].getBytes())); 的结果是 Folder-A��O��U��a��o��u���� ... - user1761231
感谢您的反馈。由于在我的机器上重新编码与原始编码相同的字符串没有任何影响(它总是打印相同的字符串),我认为这意味着两件事:MacRoman 可能不是您的默认编码,并且看起来像是一个错误。 - linski
哦,我刚看到你关于从终端运行和作为APP捆绑包的评论,由于我不熟悉OS X,这也可能意味着这不一定是一个错误 :/ - linski
显示剩余5条评论

0

0

将您的JDK降级为内置的Mac OSX JDK。如果这样做,问题应该会消失。

此外,您可能还想将Eclipse中的运行配置设置为以UTF-8方式运行。


0

这是旧版Java文件API中的一个错误(可能只在Mac上出现)。无论如何,在新版java.nio中都已经全部修复。

我有几个包含文件名和内容中Unicode字符的文件,使用java.io.File和相关类加载失败。将所有代码转换为使用java.nio.Path后,一切都开始正常工作。我还用java.nio.Files替换了具有相同问题的org.apache.commons.io.FileUtils...

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


这个问题已经在Java 7u40中被Oracle解决了。请参阅bugs.sun.com/bugdatabase/view_bug.do?bug_id=8003228。 - user1761231

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