Domanda

Sto lottando con uno strano problema di codifica del nome di file quando elencano i contenuti della directory in Java 6 su OS X e Linux: il File.listFiles() E i metodi correlati sembrano restituire nomi di file in una codifica diversa rispetto al resto del sistema.

Si noti che non è semplicemente la visualizzazione di questi nomi di file che mi sta causando problemi. Sono principalmente interessato a fare un confronto tra nomi di file con un sistema di archiviazione di file remoto, quindi mi interessa più il contenuto delle stringhe di nome rispetto alla codifica dei caratteri utilizzata per stampare l'output.

Ecco un programma da dimostrare. Crea un file con un nome Unicode, quindi stampa Codificata nell'URL versioni dei nomi dei file ottenuti dal file creato direttamente e lo stesso file se elencati in una directory genitore (è necessario eseguire questo codice in una directory vuota). I risultati mostrano la diversa codifica restituita da File.listFiles() metodo.

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

Ecco cosa ottengo quando eseguo questo codice di test sui miei sistemi. Notare la %CC contro %C3 rappresentazioni del personaggio.

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 esecuzione in una VM sullo stesso sistema 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)

Ho provato vari hack per far concordare le stringhe, incluso l'impostazione del file.encoding Proprietà del sistema e vari LC_CTYPE e LANG variabili ambientali. Nulla aiuta, né voglio ricorrere a tali hack.

A differenza di Questa domanda (un po 'correlata?), Sono in grado di leggere i dati dai file elencati nonostante i nomi dispari

È stato utile?

Soluzione

Usando Unicode, esiste più di un modo valido per rappresentare la stessa lettera. I personaggi che stai usando nel tuo nome complicato sono una "piccola lettera latina I con circonflesso" e una "piccola lettera latina a con anello sopra".

Dici "Nota il %CC contro %C3 Rappresentazioni del personaggio ", ma guardare più da vicino ciò che vedi sono le sequenze

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

Cioè, il primo è una lettera i seguito da 0xcc82 che è la codifica UTF-8 del Unicode\u0302 "Combinazione di accento circonflesso" mentre il secondo è UTF-8 per \u00EE "Piccola latina I con circonflesso". Allo stesso modo per l'altra coppia, la prima è la lettera a seguito da 0xcc8a il personaggio "combinando anello sopra" e il secondo è "latina piccola lettera a con anello sopra". Entrambi sono validi codifiche UTF-8 di stringhe di caratteri Unicode validi, ma uno è in "composto" e l'altro in formato "decomposto".

I volumi OS X HFS Plus archiviano stringhe (ad es. FileNames) come "completamente decomposte". Un sistema di file UNIX viene davvero archiviato in base al modo in cui il driver del filesystem sceglie di archiviarlo. Non è possibile fare dichiarazioni coperte tra diversi tipi di filesystem.

Guarda l'articolo di Wikipedia su Equivalenza Unicode Per la discussione generale delle forme composte vs decomposti, che menziona OS X in modo specifico.

Guarda le domande e risposte di Apple Tech QA1235 (Sfortunatamente in Objective-C) per informazioni sulla conversione di forme.

UN Discussione e -mail recente Nella mailing list Java-Dev di Apple potrebbe essere di aiuto per te.

Fondamentalmente, è necessario normalizzare la forma decomposta in una forma composta prima di poter confrontare le stringhe.

Altri suggerimenti

Soluzione estratta dalla domanda:

Grazie a Stephen P per avermi messo sulla strada giusta.

La correzione prima, per l'impaziente. Se stai compilando con Java 6 puoi usare il java.text.normalizer classe per normalizzare le stringhe in una forma comune a tua scelta, ad es.

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

Da java.text.Normalizer è disponibile solo in Java 6 e successivamente, se devi compilare con Java 5 potresti dover ricorrere al sun.text.Normalizer implementazione e qualcosa del genere Hack basato sulla riflessione Guarda anche Come funziona questa funzione normalizza?

Questo da solo mi basta per decidere che non supporterò la compilation del mio progetto con Java 5: |

Ecco altre cose interessanti che ho imparato in questa sordida avventura.

  • La confusione è causata dai nomi dei file in una delle due forme di normalizzazione che non possono essere confrontate direttamente: modulo di normalizzazione decomposizione canonica (NFD) o composizione canonica del modulo di normalizzazione (NFC). Il primo tende ad avere lettere ASCII seguite da "modificatori" per aggiungere accenti ecc., Mentre il secondo ha solo i caratteri estesi senza carattere leader di ACSCII. Leggi la pagina Wiki Stephen P Riferimenti per una spiegazione migliore.

  • Le letterali stringhe unicode come quella contenuta nel codice di esempio (e quelli ricevuti tramite HTTP nella mia app reale) sono nel modulo NFD, mentre i nomi dei file restituiti dal File.listFiles() Il metodo sono NFC. Il seguente mini-esemplare dimostra le differenze:

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

    Produzione:

    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
    
  • Se costruisci un File oggetto con un nome stringa, il File.getName() Il metodo restituirà il nome In qualunque forma gli hai dato in origine. Tuttavia, se chiami File Metodi che scoprono i nomi da soli, sembrano restituire i nomi in forma NFC. Questo è potenzialmente un cattivo gotcha. Certamente Gotchme.

  • Secondo la citazione qui sotto da Documentazione di Apple I nomi dei file sono archiviati nel modulo decomposto (NFD) sul file system HFS Plus:

    Quando lavori all'interno di Mac OS ti ritroverai a usare una miscela di Unicode precomposto e decomposto. Ad esempio, HFS Plus converte tutti i nomi di file in Unicode decomposto, mentre le tastiere Macintosh producono generalmente Unicode precomposto.

    Così la File.listFiles() Metodo in modo utile (?) Converte i nomi dei file nel modulo (pre) composto (NFC).

Ho visto qualcosa di simile prima. Persone che girano i file dal loro Mac a un nome file di file utilizzato con é.

a) Nel sistema operativo che char è normale e + "firma per ´ applicato al carbone precedente"

b) In Windows è un carattere speciale: é

Entrambi sono Unicode. Quindi ... ho capito che passi l'opzione (b) per file Crea e ad un certo punto Mac OS la converte all'opzione (a). Forse se trovi il problema della doppia rappresentazione su Internet puoi ottenere un modo per gestire entrambe le situazioni con successo.

Spero che sia d'aiuto!

Sul file system UNIX, un nome file è davvero un byte a termini null []. Quindi Java Runtime deve eseguire la conversione da java.lang.string al byte [] durante l'operazione createnewfile (). La conversione di char-byte è governata dal locale. Ho provato l'impostazione LC_ALL a en_US.UTF-8 e en_US.ISO-8859-1 E ha ottenuto risultati coerenti. Questo è con Sun (... Oracle) Java 1.6.0_20. Tuttavia, per LC_ALL=en_US.POSIX, il risultato è:

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

3F è un punto interrogativo. Mi dice che la conversione non ha avuto successo per il personaggio non ASCII. Poi di nuovo, tutto è come previsto.

Ma il motivo per cui le tue due stringhe sono diverse è a causa dell'equivalenza tra il carattere u00ee (o C3 AE in UTF-8) e la sequenza I+ u0302 (69 CC 82 in UTF-8). U0302 è un segno diacritico che combina (che combina accento circonflesso). Una sorta di normalizzazione si è verificata durante la creazione di file. Non sono sicuro che sia fatto nel tempo di esecuzione di Java o nel sistema operativo.

Nota: mi ho impiegato un po 'di tempo per capirlo poiché lo snippet del codice che hai pubblicato non ha un segno diacritico combinato ma il carattere equivalente î (per esempio \u00ee). Avresti dovuto incorporare la sequenza di Escape Unicode nella stringa letterale (ma è facile dirlo in seguito ...).

Sospetto che devi solo istruire javac cosa codifica da usare per compilare il .java File contenente i caratteri speciali con poiché lo hai codificato nel file di origine. Altrimenti verrà utilizzata la codifica predefinita della piattaforma, che potrebbe non essere affatto UTF-8.

Puoi usare l'argomento VM -encoding per questo.

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

In questo modo il risultante .class Il file finirà per contenere i caratteri corretti e sarai in grado di creare ed elencare anche il nome file corretto.

Una soluzione alternativa è quella di utilizzare la nuova API Java.Nio.Path al posto dell'API Java.io.File che funziona perfettamente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top