Frage

Ich kämpfe mit einem seltsamen Dateinamen -Codierungsproblem, wenn das Verzeichnis -Inhalt in Java 6 unter OS X und Linux aufgelistet ist: die File.listFiles() und verwandte Methoden scheinen Dateinamen in einer anderen Codierung zurückzugeben als der Rest des Systems.

Beachten Sie, dass es nicht nur die Anzeige dieser Dateinamen ist, die mir Probleme verursacht. Ich bin hauptsächlich daran interessiert, einen Vergleich von Dateinamen mit einem Remote -Dateispeichersystem durchzuführen. Daher kümmere ich mich mehr um den Inhalt der Namenszeichenfolgen als um die Zeichenkodierung, die zum Ausgeben von Ausgabe verwendet wird.

Hier ist ein Programm zum Nachweis. Es erstellt eine Datei mit einem Unicode -Namen und druckt dann aus URL-codiert Versionen der Dateinamen, die aus der direkt geschaffenen Datei erhalten wurden, und dieselbe Datei, wenn sie unter einem übergeordneten Verzeichnis aufgeführt sind (Sie sollten diesen Code in einem leeren Verzeichnis ausführen). Die Ergebnisse zeigen die unterschiedliche Codierung, die von der zurückgegeben wurden File.listFiles() Methode.

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

Folgendes bekomme ich, wenn ich diesen Testcode auf meinen Systemen ausführe. Beachten Sie das %CC gegen %C3 Charakterdarstellungen.

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 (in einem VM auf demselben OS X -System ausgeführt):

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)

Ich habe verschiedene Hacks ausprobiert, um die Saiten zuzustimmen, einschließlich der Festlegung der file.encoding Systemeigenschaft und verschiedene LC_CTYPE und LANG Umgebungsvariablen. Nichts hilft, und ich möchte auch nicht auf solche Hacks zurückgreifen.

nicht wie Diese (etwas verwandte?) Frage, Ich kann Daten aus den aufgeführten Dateien trotz der ungeraden Namen lesen

War es hilfreich?

Lösung

Mit Unicode gibt es mehr als einen gültigen Weg, um denselben Buchstaben darzustellen. Die Charaktere, die Sie in Ihrem kniffligen Namen verwenden, sind ein "lateinischer kleiner Buchstaben, den ich mit Circumflex" und ein "lateinischer kleiner Buchstaben A mit Ring oben".

Sie sagen "Notiz die %CC gegen %C3 Charakterdarstellungen ", aber genauer, was Sie sehen, sind die Sequenzen

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

Das heißt, der erste ist Brief i gefolgt von 0xcc82, das ist die UTF-8-Codierung der Unicode\u0302 "Kombinieren des Circumflex-Akzents", während der zweite UTF-8 für ist \u00EE "Latin kleiner Buchstaben I mit Circumflex". Ähnlich für das andere Paar ist der erste der Brief a gefolgt von 0xcc8a der "kombinierende Ring über" Charakter und der zweite "lateinischer kleiner Buchstaben a mit Ring oben". Beide sind gültige UTF-8-Codierungen gültiger Unicode-Zeichenzeichenfolgen, aber einer ist in "komponiert" und das andere im "zersetzten" Format.

OS X HFS Plus -Volumes speichern Zeichenfolgen (z. B. Dateinamen) als "vollständig zersetzt". Ein UNIX-Dateisystem wird wirklich gespeichert, wie der Dateisystem-Treiber es speichert. Sie können keine pauschalen Anweisungen über verschiedene Arten von Dateisystemen hinweg erstellen.

Siehe den Wikipedia -Artikel über Unicode -Äquivalenz Für die allgemeine Diskussion von komponierten VS -zersetzten Formen, in denen OS X speziell erwähnt wird.

Siehe Apples Tech Q & A QA1235 (in Objective-C leider) Informationen zum Konvertieren von Formularen.

EIN Neuerer E -Mail -Thread Auf Apples Java-Dev-Mailingliste könnte Ihnen eine Hilfe sein.

Grundsätzlich müssen Sie die zersetzte Form in eine komponierte Form normalisieren, bevor Sie die Zeichenfolgen vergleichen können.

Andere Tipps

Lösung extrahiert aus der Frage:

Vielen Dank an Stephen P, dass Sie mich auf den richtigen Weg gebracht haben.

Das Fix zuerst für die ungeduldig. Wenn Sie mit Java 6 zusammenstellen, können Sie die verwenden java.text.normalizer Klasse, um Strings in eine gemeinsame Form Ihrer Wahl zu normalisieren, z. B.

// 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;
}

Seit java.text.Normalizer ist nur in Java 6 erhältlich, und später müssen Sie möglicherweise auf das zurückgreifen, wenn Sie mit Java 5 kompilieren müssen sun.text.Normalizer Implementierung und so etwas Reflexionsbasierter Hack Siehe auch Wie funktioniert das die Funktion?

Dies allein reicht für mich aus, um zu entscheiden, dass ich die Zusammenstellung meines Projekts mit Java 5 nicht unterstützen werde: |

Hier sind andere interessante Dinge, die ich in diesem schmutzigen Abenteuer gelernt habe.

  • Die Verwirrung wird durch die Dateinamen in einer von zwei Normalisierungsformularen verursacht, die nicht direkt verglichen werden können: Normalisierungsform kanonische Zersetzung (NFD) oder Normalisierungsform Canonical Composition (NFC). Ersteres neigt dazu, ASCII -Buchstaben zu haben, gefolgt von "Modifikatoren" zum Hinzufügen von Akzenten usw., während letzteres nur die erweiterten Zeichen ohne ACSCII -führende Zeichen hat. Lesen Sie die Wiki -Seite Stephen P Referenzen für eine bessere Erklärung.

  • Unicode -String -Literale wie die im Beispielcode (und die über HTTP in meiner realen App empfangenen Personen) befinden sich im NFD -Formular, während Dateinamen von der zurückgegeben werden File.listFiles() Methode sind NFC. Das folgende Mini-Beispiel zeigt die Unterschiede:

    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"));
    

    Ausgabe:

    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
    
  • Wenn Sie a konstruieren File Objekt mit einem Stringnamen, der, der File.getName() Methode gibt den Namen zurück In welcher Form, die Sie es ursprünglich gegeben haben. Wenn Sie jedoch anrufen File Methoden, die selbst Namen entdecken, scheinen sie in NFC -Form Namen zurückzugeben. Dies ist potenziell ein böser Gotcha. Es hat sicherlich Gotchme.

  • Nach dem nachstehenden Zitat von Apples Dokumentation Die Dateinamen werden in einem zerlegten Formular (NFD) im HFS Plus -Dateisystem gespeichert:

    Wenn Sie innerhalb von Mac OS arbeiten, werden Sie eine Mischung aus vorkomponiertem und zersetztem Unicode verwenden. Beispielsweise konvertiert HFS Plus alle Dateinamen in ein Unicode, während Macintosh -Tastaturen im Allgemeinen vorkomponierte Unicode produzieren.

    Also die File.listFiles() Die Methode (?) Methode konvertiert Dateinamen in das (vor) komponierte Formular (PRE).

Ich habe schon etwas Ähnliches gesehen. Personen, die Dateien von ihrem Mac zu einem WebApp hochladen, verwendeten Dateinamen mit é.

a) In Betriebssystem ist char normal e + "Zeichen für ´ angewendet auf den vorherigen Char" "

b) In Windows ist es ein spezieller Char: é

Beide sind Unicode. Ich verstehe also, dass Sie die Option (b) zum Datieren erstellen und irgendwann Mac OS in die Option (a) konvertiert. Wenn Sie das Problem der doppelten Repräsentation über das Internet finden, können Sie möglicherweise eine Möglichkeit erhalten, beide Situationen erfolgreich zu bewältigen.

Ich hoffe es hilft!

Auf UNIX-Dateisystem ist ein Dateinamen wirklich ein Null-terminiertes Byte []. Daher muss die Java -Laufzeit während der Operation von CreateNewFile () von java.lang.String nach byte [] konvertiert werden. Die Char-to-byte-Umwandlung unterliegt dem Gebietsschema. Ich habe die Einstellung getestet LC_ALL zu en_US.UTF-8 und en_US.ISO-8859-1 und erzielte kohärente Ergebnisse. Dies ist mit Sun (... Oracle) Java 1.6.0_20. Allerdings für LC_ALL=en_US.POSIX, Das Ergebnis ist:

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

3F ist ein Fragezeichen. Es sagt mir, dass die Bekehrung für den Nicht-ASCII-Charakter nicht erfolgreich war. Andererseits ist alles wie erwartet.

Aber der Grund, warum Ihre beiden Saiten unterschiedlich sind C3 AE in UTF-8) und der Sequenz i+ u0302 (69 CC 82 in UTF-8). u0302 ist eine kombinierte diakritische Marke (kombiniert Circumflex -Akzent). Eine Art Normalisierung trat während der Dateierstellung auf. Ich bin mir nicht sicher, ob es in der Java-Laufzeit oder im Betriebssystem gemacht ist.

Hinweis: Ich habe mir einige Zeit genommen, um es herauszufinden, da der von Ihnen gepostete Code -Snippet keine kombinierte diakritische Marke hat, sondern den äquivalenten Charakter î (z.B \u00ee). Sie hätten die Unicode -Fluchtsequenz in das Saitenliteral eingebettet haben (aber es ist leicht zu sagen, dass danach ...).

Ich vermute, dass Sie nur anweisen müssen javac Welche Kodierung soll das kompilieren, um das zu kompilieren .java Datei mit den Sonderzeichen mit, da Sie sie in der Quelldatei fest codiert haben. Andernfalls wird die Plattform-Standardkodierung verwendet, die möglicherweise überhaupt nicht UTF-8 ist.

Sie können das VM -Argument verwenden -encoding dafür.

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

Auf diese Weise die resultierenden .class Die Datei enthält am Ende die richtigen Zeichen und Sie können auch den richtigen Dateinamen erstellen und auflisten.

Eine alternative Lösung besteht darin, die neue API von Java.nio.Path anstelle der API von Java.io.file zu verwenden, die perfekt funktioniert.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top