36 votes

File.listFiles () modifie les noms unicode avec JDK 6 (problèmes de normalisation Unicode)

Je suis aux prises avec un étrange nom de fichier problème de codage lorsque la liste le contenu d'un répertoire dans la version 6 de Java sur OS X et Linux: l' File.listFiles() et les méthodes connexes semblent revenir les noms de fichier dans un autre encodage que le reste du système.

Notez qu'il n'est pas simplement l'affichage de ces noms de fichiers que est me causer des problèmes. Je suis principalement intéressé à faire une comparaison des noms de fichier avec une télécommande système de stockage de fichiers, de sorte que je me soucie plus sur le contenu des chaînes de nom de l'encodage des caractères utilisé pour l'impression.

Voici un programme à démontrer. Il crée un fichier avec un nom Unicode puis imprime URL-encodé versions des noms de fichier obtenu à partir de la directement-créé le Fichier, et le fichier de même lorsqu'ils sont répertoriés sous un répertoire parent (vous devez exécuter ce code dans un répertoire vide). Les résultats montrent que le codage différent retourné par l' File.listFiles() méthode.

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

Voici ce que j'obtiens quand j'exécute ce code de test sur mes systèmes. Remarque l' %CC contre %C3 caractère représentations.

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 (exécutant sur une machine virtuelle sur le même système 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)

J'ai essayé différents hacks pour obtenir les chaînes d'accord, y compris le réglage de l' file.encoding de la propriété du système et de divers LC_CTYPE et LANG variables d'environnement. Rien n'y fait, je ne veux recours à ces hacks.

Contrairement à ce (en quelque sorte liés?) question, je suis en mesure de lire les données à partir de la liste des fichiers en dépit de l'étrange noms

Je suis à court d'idées, et après de nombreuses heures de vaines de débogage et de recherches sur Google je suis prête pour certaines lumières.

Solution

Merci à Stephen P pour me mettre sur la bonne voie.

Le correctif d'abord, pour les impatients. Si vous compilez avec la version 6 de Java, vous pouvez utiliser le java.texte.Normalisateur de classe à normaliser les chaînes en une forme courante de votre choix, par ex.

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

Depuis java.text.Normalizer n'est disponible que dans la version 6 de Java et plus tard, si vous avez besoin de compiler avec Java 5, vous pourriez avoir à recourir à l' sun.text.Normalizer de la mise en œuvre et quelque chose comme cela en fonction de réflexion hack Voir aussi Comment cette fonction de normalisation de travail?

Cela seul suffit pour moi de décider, je ne prend pas en charge la compilation de mon projet avec Java 5 :|

Voici d'autres choses intéressantes que j'ai appris dans cette sordide aventure.

  • La confusion est causée par les noms de fichier en cours dans l'une des deux formes de normalisation qui ne peuvent pas être directement comparées: la Forme de Normalisation Canonique de Décomposition (NFD) ou de la Forme de Normalisation Canonique Composition (NFC). La première tend à avoir des lettres ASCII suivi par "modificateurs" pour ajouter des accents etc, tandis que le second a seulement les caractères étendus sans ACSCII personnage principal. Lire la page wiki de Stephen P références pour une meilleure explication.

  • Unicode littéraux de chaîne comme celle contenue dans le code d'exemple (et ceux reçus via HTTP, dans mon application réelle) sont dans la NFD forme, alors que les noms de fichiers retournés par l' File.listFiles() méthode de la NFC. La suite de la mini-exemple illustre les différences:

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

    Sortie:

    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
    
  • Si vous construisez un File objet avec un nom de chaîne, l' File.getName() méthode retourne le nom quelle que soit la forme que vous lui avez donné à l'origine. Toutefois, si vous appelez File méthodes permettant de découvrir les noms sur leur propre, ils semblent revenir noms NFC forme. C'est potentiellement un méchant chat. Il a certainement gotchme.

  • D'après la citation ci-dessous à partir de la documentation d'Apple noms de fichiers sont stockés dans décomposé (DNF) de la forme sur le système de fichiers HFS Plus:

    Lorsque vous travaillez sous Mac OS, vous trouverez vous-même en utilisant un mélange de précomposé et décomposé Unicode. Par exemple, HFS Plus convertit tous les noms de fichiers à décomposée Unicode, tandis que les claviers Macintosh produisent généralement précomposé Unicode.

    Si l' File.listFiles() méthode utilement (?) convertit les noms de fichiers à la (pré)composé (NFC) dans le formulaire.

16voto

Stephen P Points 5521

À l'aide d'Unicode, il n'y a plus d'une façon valable de représenter la même lettre. Les caractères que vous utilisez dans votre Délicate Nom sont une "lettre minuscule latine i avec accent circonflexe" et une "lettre minuscule latine avec un anneau au-dessus de".

Vous dites "Note l' %CC contre %C3 caractère représentations", mais à y regarder de plus près ce que vous voyez sont les séquences

i\uCC82 vs. \uC3AE
a\uCC8A vs. \uC3A5

Le premier est la lettre i suivie par 0xCC82 la "combinant accent circonflexe" caractère tandis que la seconde est "la lettre minuscule latine i avec accent circonflexe". De même pour l'autre paire, la première est la lettre a suivie par 0xCC8A la "combinant l'anneau au-dessus de caractère", et le second est "la lettre minuscule latine avec un anneau au-dessus de". Ces deux sont valables UTF-8 codages de validité des chaînes de caractères Unicode, mais on est en "composé" et l'autre "décomposée" format.

OS X volumes HFS + de stocker des chaînes de caractères (par exemple, les noms de fichiers) "entièrement décomposé". Un fichier Unix-système est vraiment stockés en fonction de la façon dont le système de fichiers du pilote choisit de le stocker. Vous pouvez pas faire des déclarations générales sur les différents types de systèmes de fichiers.

Voir l'article de Wikipedia sur Unicode Équivalence pour la discussion générale, composé vs décomposé formes, qui mentionne OS X en particulier.

Voir Apple Tech Q&A QA1235 (en Objective-C malheureusement) pour plus d'informations sur la conversion de formes.

Un récent e-mail thread sur Apple java-dev mailing liste pourrait être d'une certaine aide pour vous.

Fondamentalement, vous avez besoin de normaliser la forme décomposée en un composé de forme avant de vous pouvez comparer les chaînes de caractères.

1voto

helios Points 8379

J'ai vu quelque chose de semblable auparavant. Les gens qui uploadde des fichiers à partir de Mac à une webapp utilisé les noms de fichiers avec l'é.

a) Dans les OS que le char est normal e + "signe appliquée à la précédente char"

b) sous Windows, c'est un char spécial: é

Les deux sont au format Unicode. Alors... je comprends que vous passez la (b) option de création de Fichier et à un certain point, Mac OS convertit le (a) en option. Peut-être que si vous trouvez la double représentation problème sur internet, vous pouvez obtenir un moyen de gérer à la fois les situations avec succès.

Espérons que cela aide!

0voto

gawi Points 5073

Sur Unix système de fichier, un nom de fichier est vraiment un null byte[]. Donc le java runtime a pour effectuer la conversion depuis java.lang.String byte[] au cours de la createNewFile() de l'opération. Le char-à-octet de conversion est régie par les paramètres régionaux. J'ai testé paramètre LC_ALL de en_US.UTF-8 et en_US.ISO-8859-1 et a obtenu des résultats cohérents. C'est avec Sun (Oracle...) java 1.6.0_20. Cependant, Pour LC_ALL=en_US.POSIX, le résultat est:

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

3F est un point d'interrogation. Il me dit que la conversion n'a pas été couronné de succès pour les caractères non-ASCII. Là encore, tout est comme prévu.

Mais la raison pour laquelle vos deux chaînes de caractères sont différents, c'est en raison de l'équivalence entre l' \u00EE caractère (ou C3 AE en UTF-8) et la séquence i+\u0302 (69 CC 82 en UTF-8). \u0302 est une combinaison de signe diacritique (combinaison d'accent circonflexe). Une sorte de normalisation s'est produite lors de la création du fichier. Je ne sais pas si c'est fait en Java au moment de l'exécution ou de l'OS.

NOTE: j'ai pris du temps à le comprendre, puisque l'extrait de code que vous avez posté n'ont pas de combinaison de signe diacritique, mais le caractère équivalent î (par exemple, \u00ee). Vous devriez avoir intégré la séquence d'échappement Unicode dans la chaîne littérale (mais c'est facile de dire que par la suite...).

0voto

BalusC Points 498232

Je soupçonne que vous avez juste à instruire javac quel encodage à utiliser pour compiler l' .java le fichier contenant les caractères spéciaux avec depuis que vous avez codé en dur dans le fichier source. Sinon, la plate-forme de codage par défaut sera utilisé, ce qui peut ne pas être en UTF-8 en tout.

Vous pouvez utiliser la VM argument -encoding pour cette.

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

De cette façon, le résultant .class fichier contenant les caractères corrects et vous serez en mesure de créer et d'indiquer le bon nom de fichier ainsi.

-2voto

user3346628 Points 9

Une autre solution consiste à utiliser la nouvelle API java.nio.Path à la place de la API java.io.File qui fonctionne parfaitement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X