Pregunta

Estoy luchando con un problema de codificación de nombre de archivo extraño al enumerar el contenido del directorio en Java 6 en OS X y Linux: el File.listFiles() y los métodos relacionados parecen devolver los nombres de archivos en una codificación diferente que el resto del sistema.

Tenga en cuenta que no es simplemente la visualización de estos nombres de archivo lo que me está causando problemas. Estoy interesado principalmente en hacer una comparación de los nombres de archivos con un sistema de almacenamiento de archivos remoto, por lo que me importa más el contenido de las cadenas de nombres que la codificación de caracteres que se usa para imprimir la salida.

Aquí hay un programa para demostrar. Crea un archivo con un nombre de unicode y luego se imprime Entendido versiones de los nombres de archivo obtenidos del archivo creado directamente y el mismo archivo cuando se enumera en un directorio principal (debe ejecutar este código en un directorio vacío). Los resultados muestran la codificación diferente devuelta por el File.listFiles() método.

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

Esto es lo que obtengo cuando ejecuto este código de prueba en mis sistemas. Nota la %CC versus %C3 Representaciones de personajes.

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 (ejecutándose en una VM en el mismo 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)

He probado varios hacks para que las cuerdas estén de acuerdo, incluida la configuración del file.encoding propiedad del sistema y varios LC_CTYPE y LANG Variables de entorno. Nada ayuda, ni quiero recurrir a tales hacks.

A diferencia de esta pregunta (algo relacionado?), Puedo leer datos de los archivos enumerados a pesar de los nombres impares

¿Fue útil?

Solución

Usando Unicode, hay más de una forma válida de representar la misma letra. Los personajes que estás usando en tu nombre difícil son una "letra latina pequeña I con circunflex" y una "letra latina pequeña con anillo arriba".

Dices "Tenga en cuenta el %CC versus %C3 Representaciones de personajes ", pero mirando más de cerca lo que ves son las secuencias

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

Es decir, el primero es letra i seguido de 0xcc82, que es la codificación UTF-8 del Unicode\u0302 "Combinar acento circunflejo", mientras que el segundo es UTF-8 para \u00EE "Letra latina pequeña I con circunflejo". Del mismo modo para el otro par, el primero es la letra a seguido de 0xcc8a el personaje de "anillo de combinación arriba" y el segundo es "letra latina pequeña con anillo arriba". Ambas son codificaciones UTF-8 válidas de cadenas de caracteres unicode válidas, pero una está en "compuesta" y la otra en formato "descompuesto".

OS X HFS Plus Volúmenes Almacenadas (por ejemplo, nombres de archivo) como "completamente descompuesto". Un sistema de archivos UNIX realmente se almacena de acuerdo con cómo el controlador del sistema de archivos elige almacenarlo. No puede hacer ninguna declaración general en diferentes tipos de sistemas de archivos.

Ver el artículo de Wikipedia en Equivalencia unicode Para la discusión general de las formas compuestas vs descompuestas, que menciona OS X específicamente.

Ver las preguntas y respuestas de la tecnología de Apple QA1235 (En Objetivo-C desafortunadamente) para obtener información sobre la conversión de formularios.

A Hilo de correo electrónico reciente En la lista de correo Java-Dev de Apple podría ser de ayuda para usted.

Básicamente, debe normalizar la forma descompuesta en una forma compuesta antes de poder comparar las cadenas.

Otros consejos

Solución extraída de la pregunta:

Gracias a Stephen P por ponerme en el camino correcto.

La solución primero, para el impaciente. Si está compilando con Java 6, puede usar el java.text.normalizer clase para normalizar las cadenas en una forma común de su elección, por ejemplo,

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

Ya que java.text.Normalizer solo está disponible en Java 6 y posterior, si necesita compilar con Java 5, es posible que tenga que recurrir a sun.text.Normalizer implementación y algo como esto hack basado en reflexión Ver también ¿Cómo funciona esta función normalizar?

Esto solo es suficiente para que decida que no apoyaré la compilación de mi proyecto con Java 5: |

Aquí hay otras cosas interesantes que aprendí en esta sórdida aventura.

  • La confusión es causada por los nombres de archivo en uno de los dos formularios de normalización que no se pueden comparar directamente: forma de normalización de descomposición canónica (NFD) o composición canónica del formulario de normalización (NFC). El primero tiende a tener letras ASCII seguidas de "modificadores" para agregar acentos, etc., mientras que el segundo solo tiene los caracteres extendidos sin carácter principal de ACSCII. Lea la página Wiki Stephen P Referencias para obtener una mejor explicación.

  • Unicode String literales como el contenido en el código de ejemplo (y los recibidos a través de HTTP en mi aplicación real) están en el formulario NFD, mientras que los nombres de archivos devueltos por el File.listFiles() El método son NFC. El siguiente mini examen demuestra las diferencias:

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

    Producción:

    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 construyes un File objeto con un nombre de cadena, el File.getName() El método devolverá el nombre En cualquier forma que le diera originalmente. Sin embargo, si llamas File Métodos que descubren nombres por su cuenta, parecen devolver los nombres en forma NFC. Este es un desagradable Gotcha. Ciertamente Gotchme.

  • Según la cita a continuación de Documentación de Apple Los nombres de los archivos se almacenan en el formulario descompuesto (NFD) en el sistema de archivos HFS Plus:

    Cuando trabaje dentro de Mac OS, se encontrará utilizando una mezcla de unicode precompuesto y descompuesto. Por ejemplo, HFS Plus convierte todos los nombres de archivo a Unicode descompuesto, mientras que los teclados Macintosh generalmente producen unicode precompuesto.

    Entonces el File.listFiles() El método útil (?) Convierte los nombres de archivo en el formulario (pre) compuesto (NFC).

He visto algo similar antes. Las personas que suben los archivos de su Mac a una aplicación web usaban nombres de archivo con é.

a) En el sistema operativo, ese char es E + "Signo para ´ aplicado al Char anterior"

b) En Windows es un char: é

Ambos son unicode. Entonces ... Entiendo que pasa la opción (b) para presentar la creación de archivos y en algún momento Mac OS lo convierte en la opción (a). Tal vez si encuentra el problema de doble representación a través de Internet, puede obtener una forma de manejar ambas situaciones con éxito.

¡Espero eso ayude!

En el sistema de archivos UNIX, un nombre de archivo realmente es un byte terminado nulo []. Entonces, el tiempo de ejecución de Java tiene que realizar la conversión de java.lang.string a byte [] durante la operación createenewfile (). La conversión de char-byte se rige por el lugar. He estado probando la configuración LC_ALL a en_US.UTF-8 y en_US.ISO-8859-1 y obtuvo resultados coherentes. Esto es con Sun (... Oracle) Java 1.6.0_20. Sin embargo, para LC_ALL=en_US.POSIX, el resultado es:

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

3F es un signo de interrogación. Me dice que la conversión no fue exitosa para el personaje no ASCII. Por otra parte, todo es como se esperaba.

Pero la razón por la cual sus dos cuerdas son diferentes es por la equivalencia entre el carácter u00ee (o C3 AE en UTF-8) y la secuencia i+ u0302 (69 CC 82 en UTF-8). u0302 es una marca diacrítica combinada (combinando acento circunflejo). Se produjo algún tipo de normalización durante la creación de archivos. No estoy seguro de si está hecho en el tiempo de ejecución de Java o en el sistema operativo.

Nota: Me tomé algo de tiempo para resolverlo ya que el fragmento de código que ha publicado no tiene una marca diacrítica combinada sino el carácter equivalente î (p.ej \u00ee). Debería haber incrustado la secuencia de escape Unicode en la cadena literal (pero es fácil decir eso después ...).

Sospecho que solo tienes que instruir javac qué codificación usar para compilar el .java Archivo que contiene los caracteres especiales ya que lo ha codificado en el archivo de origen. De lo contrario, se utilizará la codificación predeterminada de la plataforma, que puede no ser UTF-8 en absoluto.

Puedes usar el argumento de VM -encoding para esto.

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

De esta manera el resultado .class El archivo terminará conteniendo los caracteres correctos y podrá crear y enumerar el nombre de archivo correcto también.

Una solución alternativa es usar la nueva API Java.nio.Path en lugar de la API Java.io.file que funciona perfectamente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top