java - 使用 6 ( Unicode规范化问题)的java File.listFiles() mangles Unicode名称

  显示原文与译文双语对照的内容
91 5

在 OS X 和Linux上列出 Java 6中的目录内容时,我正在尝试一个奇怪的文件名编码问题: 系统其他部分相比,File.listFiles() 和相关方法似乎以不同的编码返回文件名。

注意,不仅显示了这些文件名,而且导致了我的问题。 我主要对文件名和远程文件存储系统进行比较,所以我更关心名字字符串的内容。

下面是一个演示程序。 文件使用Unicode名称创建文件,然后打印从直接创建的文件中获得的文件名称的版本,在父目录下列出相同文件。 结果显示 File.listFiles() 方法返回的编码不同。


String fileName ="Trîcky Nåme";


File file = new File(fileName);


file.createNewFile();


System.out.println("File name:" + URLEncoder.encode(file.getName(),"UTF-8"));



//Get parent (current) dir and list file contents


File parentDir = file.getAbsoluteFile().getParentFile();


File[] children = parentDir.listFiles();


for (File child: children) {


 System.out.println("Listed name:" + URLEncoder.encode(child.getName(),"UTF-8"));


}



下面是我在系统上运行这个测试代码时所得到的。 注意 %CC%C3 字符表示的。

OS X Snow Leopard:


File name: Tri%CC%82cky+Na%CC%8Ame


Listed name: Tr%C3%AEcky+N%C3%A5me



$ java -version


java version"1.6.0_20"


Java(TM) SE Runtime Environment (build 1.6.0_20-b02-279-10M3065)


Java HotSpot(TM) 64-Bit Server VM (build 16.3-b01-279, mixed mode)



KUbuntu Linux ( 在同一 OS X 系统中运行的虚拟机):


File name: Tri%CC%82cky+Na%CC%8Ame


Listed name: Tr%C3%AEcky+N%C3%A5me



$ java -version


java version"1.6.0_18"


OpenJDK Runtime Environment (IcedTea6 1.8.1) (6b18-1.8.1-0ubuntu1)


OpenJDK Client VM (build 16.0-b13, mixed mode, sharing)



为了使 file.encoding 系统性能一致,我尝试了各种攻击,包括设置系统属性和各种 LC_CTYPELANG 环境变量 。 没有什么帮助,我也不想用这种方法。

不同,这个( 有些相关) 问题,我可以从列出的文件中读取数据,尽管有?

时间: 原作者:

137 3

使用 Unicode,有多种有效的方法来表示相同的字母。 你在棘手的NAME 中使用的字符是"circumflex拉丁文小写字母i"和"上面有圈的拉丁文小写字母a"。

你说"注意 %CC%C3 字符表示的"但仔细看你看到的是序列


i 0xCC 0x82 vs. 0xC3 0xAE


a 0xCC 0x8A vs. 0xC3 0xA5



i followed followed followed xCC82 u0302/字符,而第二is为 u00EE"circumflex拉丁文小写字母i"。 它的他对类似,第一个是字母 a,后面是 0 xCC8A,"组合在上面"字符是"上面有圈的拉丁文小写字母a"。 这两个都是有效的Unicode字符字符串的UTF-8 编码,但是一个是"组合",另一个是"已经分解"格式。

OS X HFS加卷存储字符串( 比如 。 文件名) 作为"完全分解"。一个 Unix 文件系统 实际上是根据 文件系统 驱动程序选择存储它的方式存储的。 你不能在不同类型的文件系统之间进行任何blanket语句。

有关组合的分解表单的一般讨论,请参见Wikipedia关于 Unicode等价性的文章,其中提到了 OS X 。

有关转换表单的信息,请参阅苹果q&的QA1235 ( 在 objective-c 中) 。

在苹果邮件列表的java开发人员上,最近的电子邮件线程可以能有一些帮助。

基本上,在比较字符串之前,你需要将分解后的表单标准化成一个组合形式。

原作者:
76 0

从问题中提取的解决方案:

感谢 Stephen P 将我置于正确的轨道上。

首先,对于没有耐心的人。 如果使用java5编译,可以使用类将字符串标准化为你所选择的一种常见形式,例如,。


//Normalize to"Normalization Form Canonical Decomposition" (NFD)


protected String normalizeUnicode(String str) {


 Normalizer.Form form = Normalizer.Form.NFD;


 if (!Normalizer.isNormalized(str, form)) {


 return Normalizer.normalize(str, form);


 }


 return str;


}



因为 java.text.Normalizer 只能在java5和以后使用,如果需要使用Java编译,那么可能必须求助于 sun.text.Normalizer 实现 and based reflection reflection hack see this normalize function?

对于我来说,这足以让我决定不支持使用 Java 5: | 编译我的项目

下面是我在这个肮脏的冒险中学习到的其他有趣的东西。

  • 混淆是由两种正常化形式之一的文件名引起的,这些格式不能直接比较: 规范化形式分解( n 。施密特) 或者规范化规范组合( NFC ) 。 前者有"修饰符"字母后跟加音符等,而后者只有没有ACSCII前导字符的扩展字符。 阅读维基页面Stephen引用了一个更好的解释。

  • 类似于示例代码( 而那些通过我的实际应用收到的) 中包含的Unicode字符串文本,而 File.listFiles() 方法返回的文件名是 fram 。 下面的小型示例演示了这些差异:

    
    String name ="Trîcky Nåme";
    
    
    System.out.println("Original name:" + URLEncoder.encode(name,"UTF-8"));
    
    
    System.out.println("NFC Normalized name:" + URLEncoder.encode(
    
    
     Normalizer.normalize(name, Normalizer.Form.NFC),"UTF-8"));
    
    
    System.out.println("NFD Normalized name:" + URLEncoder.encode(
    
    
     Normalizer.normalize(name, Normalizer.Form.NFD),"UTF-8"));
    
    
    
    

    输出:

    
    Original name: Tri%CC%82cky+Na%CC%8Ame
    
    
    NFC Normalized name: Tr%C3%AEcky+N%C3%A5me
    
    
    NFD Normalized name: Tri%CC%82cky+Na%CC%8Ame
    
    
    
    
  • 如果使用字符串名称构造一个 File 对象,则 File.getName() 方法将以任何形式返回名称,以任何形式为你指定的格式。 但是,如果你调用了 File 方法,它们自己发现名称,那么它们似乎会返回in表单的名称。 这可能是一个错误的发现。 当然是 gotchme 。

  • 文件的下面引用quote文件名存储在HFS加文件系统的分解的窗体中:

    在macosx中工作时,你会发现自己使用了precomposed和分解的Unicode 。 例如 HFS + 将所有文件名转换为分解的Unicode,而Macintosh键盘通常产生 precomposed Unicode 。

    File.listFiles() 方法帮助() 将文件名转换为( 预先)的( NFC ) 表单。

...