Problema de codificación de Java FileReader

130

Intenté usar java.io.FileReader para leer algunos archivos de texto y convertirlos en una cadena, pero descubrí que el resultado está codificado incorrectamente y no es legible en absoluto.

Aquí está mi entorno:

  • Windows 2003, codificación del sistema operativo: CP1252

  • Java 5.0

Mis archivos están codificados en UTF-8 o CP1252, y algunos de ellos (archivos codificados en UTF-8) pueden contener caracteres chinos (no latinos).

Yo uso el siguiente código para hacer mi trabajo:

   private static String readFileAsString(String filePath)
    throws java.io.IOException{
        StringBuffer fileData = new StringBuffer(1000);
        FileReader reader = new FileReader(filePath);
        //System.out.println(reader.getEncoding());
        BufferedReader reader = new BufferedReader(reader);
        char[] buf = new char[1024];
        int numRead=0;
        while((numRead=reader.read(buf)) != -1){
            String readData = String.valueOf(buf, 0, numRead);
            fileData.append(readData);
            buf = new char[1024];
        }
        reader.close();
        return fileData.toString();
    }

El código anterior no funciona. Descubrí que la codificación del FileReader es CP1252 incluso si el texto está codificado en UTF-8. Pero el JavaDoc de java.io.FileReader dice que:

Los constructores de esta clase asumen que la codificación de caracteres predeterminada y el tamaño de byte-buffer predeterminado son apropiados.

¿Significa esto que no estoy obligado a configurar la codificación de caracteres por mi cuenta si estoy usando FileReader? Pero obtuve datos codificados incorrectamente actualmente, ¿cuál es la forma correcta de lidiar con mi situación? Gracias.

nybon
fuente
También debe perder el String.valueOf () dentro del bucle y usar StringBuffer.append (char [], int, int) directamente. Esto ahorra muchas copias de char []. También reemplace StringBuffer con StringBuilder. Sin embargo, nada de esto es sobre su pregunta '.
Joachim Sauer
1
Odio decirlo, pero ¿has leído el JavaDoc justo después de la parte que pegaste? Ya sabes, la parte que dice "Para especificar estos valores usted mismo, construya un InputStreamReader en un FileInputStream".
Powerlord
Gracias por su comentario, en realidad leí el JavaDoc, pero de lo que no estoy seguro es si debería especificar estos valores yo mismo y cambiar a "construir un InputStreamReader en un FileInputStream".
nybon
Sí, si sabe que el archivo está en algo diferente a la codificación predeterminada de la plataforma, debe decirle al InputStreamReader cuál usar.
Alan Moore el

Respuestas:

248

Sí, debe especificar la codificación del archivo que desea leer.

Sí, esto significa que debe conocer la codificación del archivo que desea leer.

No, no hay una forma general de adivinar la codificación de cualquier archivo de "texto plano".

Los constructores de un argumentoFileReader siempre usan la codificación predeterminada de la plataforma, que generalmente es una mala idea .

Desde Java 11 FileReadertambién ha ganado constructores que aceptan una codificación: new FileReader(file, charset)y new FileReader(fileName, charset).

En versiones anteriores de java, debe usar .new InputStreamReader(new FileInputStream(pathToFile), <encoding>)

Joachim Sauer
fuente
1
InputStream es = nuevo FileInputStream (nombre de archivo); aquí tengo error de archivo de error que no se encuentra con el nombre de archivo de Rusia
Bhanu Sharma
3
+1 para la sugerencia de usar InputStreamReader, sin embargo, usar enlaces en bloques de código hace que sea difícil copiar y pegar el código, si esto se puede cambiar, gracias
Ferrybig
1
Sería "UTF-8" o "UTF8" en las codificaciones. Según la referencia de Java SE sobre codificación , dado que InputStreamReaderes una java.ioclase, ¿sería "UTF8"?
NobleUplift
9
@NobleUplift: la apuesta más segura es que StandardCharsets.UTF_8no hay posibilidad de escribir mal allí ;-) Pero sí, si vas con una cuerda "UTF8"sería correcto (aunque parece recordar que aceptará ambas cosas).
Joachim Sauer
1
@JoachimSauer En realidad, este es uno de los propósitos de Byte Order Mark, junto con ... bueno ... ¡establecer el orden de bytes! :) Como tal, me parece extraño que FileReader de Java no pueda detectar automáticamente UTF-16 que tiene una BOM de ese tipo ... De hecho, una vez escribí una UnicodeFileReaderque hace exactamente eso. Desafortunadamente fuente cerrada, pero Google tiene su UnicodeReader que es muy similar.
Stijn de Witt
79

FileReader utiliza la codificación predeterminada de la plataforma Java, que depende de la configuración del sistema de la computadora en la que se está ejecutando y generalmente es la codificación más popular entre los usuarios de esa configuración regional.

Si esta "mejor suposición" no es correcta, entonces debe especificar la codificación explícitamente. Lamentablemente, FileReaderno permite esto (supervisión importante en la API). En su lugar, debe usar new InputStreamReader(new FileInputStream(filePath), encoding)e idealmente obtener la codificación de los metadatos sobre el archivo.

Michael Borgwardt
fuente
24
"gran supervisión en la API" - gracias por esta explicación - ¡Me preguntaba por qué no pude encontrar el constructor que buscaba! Cheers John
monojohnny
@Bhanu Sharma: ese es un problema de codificación en un nivel diferente, verifique de dónde obtiene el nombre de archivo y si está codificado qué codificación utiliza el compilador.
Michael Borgwardt
1
@BhanuSharma: los problemas de codificación de nombre de archivo no tienen nada que ver con esta pregunta. Vea una de las muchas preguntas existentes "por qué los nombres de archivo Unicode no funcionan en Java". Spoiler: las API de java.io como FileReader usan llamadas al sistema de archivos de la biblioteca estándar C, que no pueden admitir Unicode en Windows; considere usar java.nio en su lugar.
bobince
1
" FileReaderutiliza la codificación predeterminada de la plataforma Java, que depende de la configuración del sistema de la computadora en la que se está ejecutando y, en general, es la codificación más popular entre los usuarios de esa configuración regional". Yo no diría eso. Al menos de Windows. Por algunas extrañas razones técnicas / históricas, la JVM ignora el hecho de que Unicode es la codificación recomendada en Windows para 'todas las aplicaciones nuevas' y en su lugar siempre actúa como si la codificación heredada configurada como reserva para aplicaciones heredadas sea ​​el 'valor predeterminado de la plataforma'.
Stijn de Witt
66
Incluso diría que si su aplicación Java no especifica explícitamente las codificaciones cada vez que lee o escribe en archivos / secuencias / recursos, está dañada , porque entonces no puede funcionar de manera confiable.
Stijn de Witt
8

Desde Java 11 puede usar eso:

public FileReader(String fileName, Charset charset) throws IOException;
Radoslav Ivanov
fuente
6

Para Java 7+ doc puede usar esto:

BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8);

Aquí están todos los documentos de Charsets

Por ejemplo, si su archivo está en CP1252, use este método

Charset.forName("windows-1252");

Aquí hay otros nombres canónicos para codificaciones Java tanto para IO como para NIO doc.

Si usted no sabe con codificación es exactamente lo que tienes en un archivo, es posible utilizar algunas librerías de terceros, como esta herramienta de Google este , que funciona bastante bien cuidado.

Andreas Gelever
fuente
1

FileInputStream con InputStreamReader es mejor que usar directamente FileReader, porque este último no le permite especificar el conjunto de caracteres de codificación.

Aquí hay un ejemplo usando BufferedReader, FileInputStream y InputStreamReader juntos, para que pueda leer las líneas de un archivo.

List<String> words = new ArrayList<>();
List<String> meanings = new ArrayList<>();
public void readAll( ) throws IOException{
    String fileName = "College_Grade4.txt";
    String charset = "UTF-8";
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(
            new FileInputStream(fileName), charset)); 

    String line; 
    while ((line = reader.readLine()) != null) { 
        line = line.trim();
        if( line.length() == 0 ) continue;
        int idx = line.indexOf("\t");
        words.add( line.substring(0, idx ));
        meanings.add( line.substring(idx+1));
    } 
    reader.close();
}
Guangtong Shen
fuente
0

Para otro como idiomas latinos, por ejemplo cirílico, puede usar algo como esto:

FileReader fr = new FileReader("src/text.txt", StandardCharsets.UTF_8);

y asegúrese de que su .txtarchivo se guarde con el formato UTF-8(pero no como predeterminado ANSI). ¡Salud!

Iefimenko Ievgwn
fuente