Problema de codificação do Java FileReader

130

Tentei usar o java.io.FileReader para ler alguns arquivos de texto e convertê-los em uma string, mas descobri que o resultado está codificado incorretamente e não é legível.

Aqui está o meu ambiente:

  • Windows 2003, codificação do sistema operacional: CP1252

  • Java 5.0

Meus arquivos são codificados em UTF-8 ou CP1252 e alguns deles (arquivos em UTF-8) podem conter caracteres chineses (não latinos).

Eu uso o seguinte código para fazer o meu trabalho:

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

O código acima não funciona. Eu descobri que a codificação do FileReader é CP1252, mesmo que o texto seja codificado em UTF-8. Mas o JavaDoc do java.io.FileReader diz que:

Os construtores dessa classe assumem que a codificação de caracteres padrão e o tamanho padrão do buffer de bytes são adequados.

Isso significa que não sou obrigado a definir a codificação de caracteres sozinho se estiver usando o FileReader? Mas atualmente recebi dados codificados incorretamente. Qual é a maneira correta de lidar com minha situação? Obrigado.

nybon
fonte
Você também deve perder o String.valueOf () dentro do loop e usar StringBuffer.append (char [], int, int) diretamente. Isso economiza muitas cópias do caractere []. Substitua também StringBuffer por StringBuilder. Porém, nada disso é sobre sua pergunta.
Joachim Sauer
1
Detesto dizer isso, mas você leu o JavaDoc logo após a parte que colou? Você sabe, a parte que diz "Para especificar esses valores você mesmo, construa um InputStreamReader em um FileInputStream".
Powerlord 30/03/09
Obrigado pelo seu comentário, na verdade eu li o JavaDoc, mas o que não tenho certeza é se devo ou não especificar esses valores e mudar para "construir um InputStreamReader em um FileInputStream".
nybon 31/03/09
Sim, se você souber que o arquivo está em algo diferente da codificação padrão da plataforma, será necessário informar ao InputStreamReader qual usar.
Alan Moore

Respostas:

248

Sim, você precisa especificar a codificação do arquivo que deseja ler.

Sim, isso significa que você precisa saber a codificação do arquivo que deseja ler.

Não, não existe uma maneira geral de adivinhar a codificação de qualquer arquivo de "texto simples".

Os construtores de um argumentoFileReader sempre usam a codificação padrão da plataforma, que geralmente é uma má ideia .

Desde que o Java 11 FileReadertambém ganhou construtores que aceitam uma codificação: new FileReader(file, charset)e new FileReader(fileName, charset).

Nas versões anteriores do java, você precisa usar .new InputStreamReader(new FileInputStream(pathToFile), <encoding>)

Joachim Sauer
fonte
1
InputStream é = novo FileInputStream (nome do arquivo); aqui eu tenho o arquivo de erro não encontrado erro com o nome de arquivo russo
Bhanu Sharma
3
1 para a sugestão de usar InputStreamReader, no entanto usando links em blocos de código faz com que seja difícil de copiar e colar o código, se isso pode ser alterado, thx
Ferrybig
1
Seria "UTF-8" ou "UTF8" nas codificações. De acordo com a referência do Java SE em codificação , uma vez que InputStreamReaderé uma java.ioclasse, seria "UTF8"?
NobleUplift 13/11/2015
9
@NobleUplift: a aposta mais segura é que StandardCharsets.UTF_8não há chance de erros de digitação ;-) Mas sim, se você usar uma string, "UTF8"ela estará correta (embora pareço lembrar que ela aceita os dois lados).
Joachim Sauer
1
@JoachimSauer Na verdade, esse é um dos objetivos do Byte Order Mark, juntamente com .. bem .. estabelecer a ordem dos bytes! :) Como tal, acho estranho que o FileReader do Java não seja capaz de detectar automaticamente o UTF-16 que possui uma lista técnica ... Na verdade, eu escrevi uma vez UnicodeFileReaderque faz exatamente isso. Infelizmente, código fonte fechado, mas o Google tem o UnicodeReader, que é muito semelhante.
Stijn de Witt
79

FileReader usa a codificação padrão da plataforma Java, que depende das configurações do sistema do computador em que está sendo executado e geralmente é a codificação mais popular entre os usuários nesse local.

Se esse "melhor palpite" não estiver correto, será necessário especificar a codificação explicitamente. Infelizmente, FileReaderisso não é permitido (grande supervisão na API). Em vez disso, você deve usar new InputStreamReader(new FileInputStream(filePath), encoding)e, idealmente, obter a codificação a partir de metadados sobre o arquivo.

Michael Borgwardt
fonte
24
"grande supervisão na API" - obrigado por essa explicação - eu estava pensando por que não consegui encontrar o construtor que procurava! Cheers John
monojohnny
@Bhanu Sharma: esse é um problema de codificação em um nível diferente, verifique de onde você está obtendo o nome do arquivo e, se está codificado, qual codificação o compilador usa.
Michael Borgwardt
1
@BhanuSharma: problemas de codificação de nome de arquivo não têm nada a ver com esta pergunta. Veja uma das muitas perguntas existentes sobre "por que os nomes de arquivos Unicode não funcionam em Java"? Spoiler: APIs java.io como FileReader usam chamadas do sistema de arquivos da biblioteca padrão C, que não suportam Unicode no Windows; considere usar java.nio.
22915
1
" FileReaderusa a codificação padrão da plataforma Java, que depende das configurações do sistema do computador em que está sendo executado e geralmente é a codificação mais popular entre os usuários nesse local". Eu não diria isso. Pelo menos do Windows. Por algumas razões técnicas / históricas estranhas, a JVM ignora o fato de que Unicode é a codificação recomendada no Windows para 'todos os novos aplicativos' e, em vez disso, sempre age como se a codificação herdada configurada como fallback para aplicativos herdados fosse o 'padrão da plataforma'.
Stijn de Witt
6
Eu diria até que, se seu aplicativo Java não especificar explicitamente codificações toda vez que estiver lendo ou gravando em arquivos / fluxos / recursos, ele será quebrado , porque nunca poderá funcionar de maneira confiável.
Stijn de Witt
8

Desde o Java 11, você pode usar isso:

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

Para o Java 7+ doc, você pode usar este:

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

Aqui estão todos os documentos de charsets

Por exemplo, se o seu arquivo estiver no CP1252, use este método

Charset.forName("windows-1252");

Aqui estão outros nomes canônicos para codificações Java, tanto para documentos de E / S quanto de NIO

Se você não sabe com codificação exatamente você tem em um arquivo, você pode usar algumas libs de terceiros como esta ferramenta do Google este que funciona razoavelmente arrumado.

Andreas Gelever
fonte
1

FileInputStream com InputStreamReader é melhor do que usar diretamente o FileReader, porque o último não permite especificar o conjunto de caracteres de codificação.

Aqui está um exemplo usando BufferedReader, FileInputStream e InputStreamReader juntos, para que você possa ler linhas de um arquivo.

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
fonte
0

Para outros idiomas latinos, por exemplo, cirílico, você pode usar algo como isto:

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

e verifique se seu .txtarquivo foi salvo com o formato UTF-8(mas não como padrão ANSI). Felicidades!

Iefimenko Ievgwn
fonte