18

在使用 Oracle 的 Java 7 时,我在使用 File.list() 时遇到问题,其文件名中的非 ASCII 字符在 Mac OS X 上被错误地检索。

我正在使用以下示例:

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(); 
    } 
  }

}

使用 Apple 的 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 中无效(另请参见此处

如何在 Mac OS X 上的 Oracle 中正确检索 Java 7 中的文件名,而无需设置 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 

所以可以看到 Files.list() 用 UTF-8 "EF BF BD" = Unicode U+FFFD = Replacement Character 替换了一些字节,如果 LANG 没有设置(只有 Oracle 的 Java 7)。

4

5 回答 5

4

如果其他一切都失败了,请为设置 LC_CTYPE 环境变量的 JVM 创建一个包装器,然后启动您的应用程序。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(Apple 或 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());
    }
  }
}

当我locale在 OS 10.7 上运行时,我得到了这个输出。在我的系统上,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=
于 2012-10-20T11:46:20.123 回答
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()));

因此,如果这也不起作用,那么就会得出结论,正如 Andrew Thomson 在对您的问题的评论中所说的那样,这可能是一个错误。

于 2012-10-20T09:59:53.487 回答
0

这是 OpenJDK 中的一个已知错误。OS X 10.6 和 OS X 10.7 为默认语言环境返回不同的值。请参阅错误http://java.net/jira/browse/MACOSX_PORT-204http://java.net/jira/browse/MACOSX_PORT-165。如果您遇到此问题,请投票支持解决此问题。

于 2012-12-27T14:04:10.440 回答
0

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

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

于 2013-08-12T20:51:00.540 回答
0

这是旧的 java File api 中的一个错误(可能只是在 mac 上)。无论如何,这一切都在新的 java.nio 中得到了修复。

我有几个文件在文件名和内容中包含无法使用 java.io.File 和相关类加载的 unicode 字符。在将我的所有代码转换为使用java.nio.Path之后,一切都开始工作了。我用java.nio.Files替换了 org.apache.commons.io.FileUtils (有同样的问题) ......

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

于 2014-02-24T13:58:03.500 回答