36

OS X と Linux の両方で Java 6 のディレクトリ コンテンツを一覧表示するときに、奇妙なファイル名エンコーディングの問題に苦しんでいます。File.listFiles()および 関連するメソッドは、システムの残りの部分とは異なるエンコーディングでファイル名を返すようです。

問題を引き起こしているのは、これらのファイル名の表示だけではないことに注意してください。私は主にファイル名をリモート ファイル ストレージ システムと比較することに関心があるので、出力に使用される文字エンコーディングよりも名前文字列の内容に関心があります。

ここにデモンストレーションするプログラムがあります。Unicode 名でファイルを作成し、直接作成されたファイルから取得したファイル名のURL エンコードバージョンを出力し、親ディレクトリの下にリストされている場合は同じファイルを出力します (このコードは空のディレクトリで実行する必要があります)。結果は、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 システム上の VM で実行):

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_CTYPE環境LANG変数の設定など、文字列を一致させるためにさまざまなハックを試しました。何も役に立ちませんし、そのようなハックに頼りたくもありません。

この(やや関連する)質問とは異なり、奇妙な名前にもかかわらず、リストされたファイルからデータを読み取ることができます

4

6 に答える 6

16

Unicode を使用すると、同じ文字を表す有効な方法が複数あります。Tricky Name で使用している文字は、「サーカムフレックス付きラテン小文字 i」と「上にリング付きラテン小文字 a」です。

%CCあなたは「対文字表現に注意してください」と言い%C3ますが、よく見るとシーケンスが表示されます

i 0xCC 0x82 vs. 0xC3 0xAE
a 0xCC 0x8A vs. 0xC3 0xA5

つまり、最初の文字は文字のi後に 0xCC82 が続きます。これはUnicode\u0302の「サーカムフレックス アクセントを組み合わせた」文字の UTF-8 エンコードであり、2 番目は「\u00EEサーカムフレックス付きラテン小文字 i」の UTF-8 です。他のペアについても同様に、最初の文字は文字のa後に 0xCC8A が続き、「上のリングを結合する」文字であり、2 番目は「上にリングのあるラテン小文字 a」です。どちらも有効な Unicode 文字列の有効な UTF-8 エンコーディングですが、一方は「合成」形式で、もう一方は「分解」形式です。

OS X HFS Plus ボリュームは、文字列 (ファイル名など) を「完全に分解」して保存します。Unix ファイルシステムは、ファイルシステム ドライバーが選択した保存方法に従って実際に保存されます。異なるタイプのファイルシステムにまたがってブランケット ステートメントを作成することはできません。

合成フォームと分解フォームの一般的な議論については、Unicode の等価性に関するウィキペディアの記事を参照してください。特に OS X について言及しています。

フォームの変換については、 Apple の Tech Q&A QA1235 (残念ながら Objective-C) を参照してください。

Apple の java-dev メーリング リストにある最近の電子メール スレッドが役立つかもしれません。

基本的に、文字列を比較する前に、分解された形式を合成された形式に正規化する必要があります。

于 2010-09-01T00:33:00.037 に答える
2

質問から抽出された解決策:

私を正しい軌道に乗せてくれた Stephen P に感謝します。

せっかちな人のために、最初に修正します。Java 6 でコンパイルしている場合は、java.text.Normalizerクラスを使用して、文字列を選択した一般的な形式に正規化できます。

// 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 6以降java.text.Normalizerでのみ使用できるため、Java 5 でコンパイルする必要がある場合は、このリフレクション ベースのハックのsun.text.Normalizerような実装に頼る必要があるかもしれません

Java 5 を使用したプロジェクトのコンパイルをサポートしないと判断するには、これだけで十分です :|

この下劣な冒険で私が学んだその他の興味深いことを次に示します。

  • この混乱は、ファイル名が直接比較できない 2 つの正規化形式 (Normalization Form Canonical Decomposition (NFD) または Normalization Form Canonical Composition (NFC)) のいずれかであるために発生します。前者は ASCII 文字の後にアクセントなどを追加する「修飾子」が続く傾向がありますが、後者は拡張文字のみで ACSCII の先頭文字はありません。より良い説明については、Wiki ページ Stephen P の参照を参照してください。

  • サンプル コードに含まれているような Unicode 文字列リテラル (および実際のアプリで HTTP 経由で受信したもの) は NFD 形式ですが、File.listFiles()メソッドによって返されるファイル名は NFC です。次のミニ例は、違いを示しています。

    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()名前を返します。ただし、独自に名前を検出するメソッドを呼び出すと、名前が NFC 形式で返されるようです。これは潜在的に厄介な落とし穴です。それは確かに得ました。File

  • 以下の Apple のドキュメントからの引用によると、ファイル名は HFS Plus ファイル システムに分解 (NFD) 形式で保存されます。

    Mac OS で作業していると、事前に構成された Unicode と分解された Unicode が混在して使用されていることに気付くでしょう。たとえば、HFS Plus はすべてのファイル名を分解された Unicode に変換しますが、Macintosh キーボードは通常、事前に合成された Unicode を生成します。

    そのため、このFile.listFiles()メソッドは便利に (?) ファイル名を (事前に) 構成された (NFC) 形式に変換します。

于 2015-09-19T03:33:52.420 に答える
1

以前似たようなものを見たことがあります。Mac から Web アプリケーションにファイルをアップロードする人は、ファイル名に é を使用していました。

a) その文字が通常の OS では e + "前の文字に適用される ´ の符号"

b) Windows では、それは特別な文字です: é

どちらもユニコードです。それで... (b) オプションを File create に渡し、ある時点で Mac OS がそれを (a) オプションに変換することを理解しています。おそらく、インターネット上で二重表現の問題を見つけた場合、両方の状況をうまく処理する方法を手に入れることができます.

それが役に立てば幸い!

于 2010-08-31T14:51:17.913 に答える
0

ソースファイルにハードコーディングしたので、特殊文字を含むファイルをjavacコンパイルするために使用するエンコーディングを指示するだけでよいと思います。.javaそれ以外の場合は、プラットフォームのデフォルトのエンコーディングが使用されますが、これは UTF-8 ではない場合があります。

これには VM 引数-encodingを使用できます。

javac -encoding UTF-8 com/example/Foo.java

このようにして、結果の.classファイルに正しい文字が含まれるようになり、正しいファイル名を作成して一覧表示することもできます。

于 2010-08-31T19:34:13.957 に答える
0

Unix ファイルシステムでは、ファイル名は実際には null で終わる byte[] です。そのため、java ランタイムは createNewFile() 操作中に java.lang.String から byte[] への変換を実行する必要があります。文字からバイトへの変換は、ロケールによって管理されます。LC_ALLおよび への設定をテストしてen_US.UTF-8おりen_US.ISO-8859-1、一貫した結果が得られました。これは、Sun (...Oracle) Java 1.6.0_20 を使用しています。ただし、 の場合LC_ALL=en_US.POSIX、結果は次のようになります。

File name:   Tr%C3%AEcky+N%C3%A5me
Listed name: Tr%3Fcky+N%3Fme

3Fは疑問符です。非ASCII文字の変換が成功しなかったことがわかります。繰り返しますが、すべてが期待どおりです。

しかし、2 つの文字列が異なる理由は、\u00EE 文字 (またはC3 AEUTF-8) とシーケンス i+\u0302 ( 69 CC 82UTF-8) が同等であるためです。\u0302 は結合分音記号 (サーカムフレックス アクセントの結合) です。ファイルの作成中に何らかの正規化が行われました。JavaランタイムまたはOSで行われるかどうかはわかりません。

注:あなたが投稿したコードスニペットには、発音区別記号を組み合わせたものではなく、同等の文字î(例:\u00ee)がないため、理解するのに少し時間がかかりました。文字列リテラルに Unicode エスケープ シーケンスを埋め込んでおく必要があります (後で言うのは簡単です...)。

于 2010-08-31T16:06:30.103 に答える
-2

別の解決策は、完全に機能する java.io.File API の代わりに新しい java.nio.Path API を使用することです。

于 2014-03-05T10:04:41.570 に答える