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 ) 表單。

...