File.list() 在 Mac OS X 上从 Oracle 使用 Java 7 时错误地检索带有非 ASCII 字符的文件名

2022-09-02 22:34:48

我在使用File.list()时遇到问题,在使用Oracle的Java 7时,在Mac OS X上错误地检索了带有非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(); 
    } 
  }

}

使用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 应用程序,这样做,LS 环境设置

<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 

因此,如果 LANG 未设置,则可以看到 Files.list() 将一些字节替换为 UTF-8 “EF BF BD” = Unicode U+FFFD = 替换字符(仅来自 Oracle 的 Java 7)。


答案 1

如果其他所有操作都失败,请为 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(来自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(由 system 属性显示)。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上运行时,我得到这个输出。似乎在我的系统上,Java 6无法正确解释为LC_CTYPE给出的值。据我所知,系统没有自定义,所有内容都设置为英语,因此这应该是默认配置:locale

LANG=
LC_COLLATE="C"
LC_CTYPE="UTF-8"
LC_MESSAGES="C"
LC_MONETARY="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_ALL=

答案 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)在评论您的问题时所说的那样,这可能是一个错误。