Java: Como determinar a codificação correta de um córrego

votos
109

Com referência à seguinte discussão: Java App: Não é possível ler arquivo codificado iso-8859-1 corretamente

Qual é a melhor maneira de determinar programaticamente a codificação correta de um inputstream / arquivo?

Eu tentei usar o seguinte:

File in =  new File(args[0]);
InputStreamReader r = new InputStreamReader(new FileInputStream(in));
System.out.println(r.getEncoding());

Mas em um arquivo que eu conheço para ser codificado com ISO8859_1 a rendimentos código acima ASCII, que não é correto, e não me permite processar corretamente o conteúdo do arquivo de volta para o console.

Publicado 31/01/2009 em 16:34
fonte usuário
Em outras línguas...                            


15 respostas

votos
90

Você não pode determinar a codificação de um fluxo de bytes arbitrário. Esta é a natureza de codificações. Um meio de codificação um mapeamento entre um valor de byte e sua representação. Então, toda a codificação "poderia" ser o certo.

O getEncoding () método de codificação voltará a qual foi configurada (ler o JavaDoc ) para o fluxo. Não vai adivinhar a codificação para você.

Alguns fluxos dizer qual codificação foi usada para criá-los: XML, HTML. Mas não um fluxo de bytes arbitrário.

De qualquer forma, você poderia tentar adivinhar uma codificação em seu próprio país, se você tiver que. Cada língua tem uma frequência comum para cada caractere. Em Inglês o caractere e aparece com muita frequência, mas ê vai aparecer muito raramente. Em uma corrente ISO-8859-1 que geralmente não há 0x00 caracteres. Mas um fluxo UTF-16 tem um monte deles.

Ou: você poderia perguntar ao usuário. Eu já vi aplicações que apresentam você um trecho do arquivo em diferentes codificações e pedir-lhe para escolher o caminho "correto".

Respondeu 31/01/2009 em 16:44
fonte usuário

votos
60

Eu tenho usado essa biblioteca, semelhante ao jchardet para a detecção de codificação em Java: http://code.google.com/p/juniversalchardet/

Respondeu 19/01/2011 em 14:44
fonte usuário

votos
31

veja isso: http://site.icu-project.org/ (ICU4J) têm bibliotecas para detecção de charset de iostream poderia ser simples como isto:

BufferedInputStream bis = new BufferedInputStream(input);
CharsetDetector cd = new CharsetDetector();
cd.setText(bis);
CharsetMatch cm = cd.detect();

if (cm != null) {
   reader = cm.getReader();
   charset = cm.getName();
}else {
   throw new UnsupportedCharsetException()
}
Respondeu 25/10/2010 em 11:11
fonte usuário

votos
20

Aqui estão os meus favoritos:

TikaEncodingDetector

Dependência:

<dependency>
  <groupId>org.apache.any23</groupId>
  <artifactId>apache-any23-encoding</artifactId>
  <version>1.1</version>
</dependency>

Amostra:

public static Charset guessCharset(InputStream is) throws IOException {
  return Charset.forName(new TikaEncodingDetector().guessEncoding(is));    
}

GuessEncoding

Dependência:

<dependency>
  <groupId>org.codehaus.guessencoding</groupId>
  <artifactId>guessencoding</artifactId>
  <version>1.4</version>
  <type>jar</type>
</dependency>

Amostra:

  public static Charset guessCharset2(File file) throws IOException {
    return CharsetToolkit.guessEncoding(file, 4096, StandardCharsets.UTF_8);
  }
Respondeu 30/11/2014 em 12:48
fonte usuário

votos
12

Você certamente pode validar o arquivo para um charset nomeadamente através decodificação -lo com um CharsetDecodere cuidando de "malformado de entrada" ou erros "unmappable caracteres". Claro, isso só lhe diz se um charset está errado; ele não lhe dizer se ele está correto. Para isso, você precisa de uma base de comparação para avaliar os resultados decodificados, por exemplo, você sabe de antemão se os personagens são restritos a um subconjunto, ou se o texto segue algum formato estrito? O resultado final é que a detecção do conjunto de caracteres é conjecturas, sem quaisquer garantias.

Respondeu 01/02/2009 em 08:33
fonte usuário

votos
6

Os libs acima são simples detectores de BOM que, naturalmente, só funcionam se houver um BOM no início do arquivo. Dê uma olhada em http://jchardet.sourceforge.net/ que faz procura no texto

Respondeu 15/02/2010 em 12:53
fonte usuário

votos
5

Se você usar ICU4J ( http://icu-project.org/apiref/icu4j/ )

Aqui está o meu código:

            String charset = "ISO-8859-1"; //Default chartset, put whatever you want

            byte[] fileContent = null;
            FileInputStream fin = null;

            //create FileInputStream object
            fin = new FileInputStream(file.getPath());

            /*
             * Create byte array large enough to hold the content of the file.
             * Use File.length to determine size of the file in bytes.
             */
            fileContent = new byte[(int) file.length()];

            /*
             * To read content of the file in byte array, use
             * int read(byte[] byteArray) method of java FileInputStream class.
             *
             */
            fin.read(fileContent);

            byte[] data =  fileContent;

            CharsetDetector detector = new CharsetDetector();
            detector.setText(data);

            CharsetMatch cm = detector.detect();

            if (cm != null) {
                int confidence = cm.getConfidence();
                System.out.println("Encoding: " + cm.getName() + " - Confidence: " + confidence + "%");
                //Here you have the encode name and the confidence
                //In my case if the confidence is > 50 I return the encode, else I return the default value
                if (confidence > 50) {
                    charset = cm.getName();
                }
            }

Lembre-se de colocar toda a try catch precisa.

Espero que isso funcione pra você.

Respondeu 04/04/2013 em 22:01
fonte usuário

votos
5

Eu encontrei uma biblioteca de terceiros agradável que pode detectar codificação real: http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding

Eu não testá-lo extensivamente, mas parece funcionar.

Respondeu 07/01/2010 em 10:04
fonte usuário

votos
4

Se você não sabe a codificação dos seus dados, não é tão fácil de determinar, mas você pode tentar usar uma biblioteca de adivinhar . Além disso, há uma pergunta semelhante .

Respondeu 31/01/2009 em 16:46
fonte usuário

votos
3

Tanto quanto eu sei, não há nenhuma biblioteca geral, neste contexto, ser adequado para todos os tipos de problemas. Assim, para cada problema você deve testar as bibliotecas existentes e selecionar o melhor que satisfaz as restrições é o seu problema, mas muitas vezes nenhum deles é apropriado. Nestes casos, você pode escrever seu próprio detector de codificação! Como já escreveu ...

Eu escrevi uma ferramenta java meta para a detecção de codificação charset de páginas da Web em HTML, usando o IBM ICU4J e Mozilla JCharDet como os componentes internos. Aqui pode encontrar a minha ferramenta, por favor, leia a seção README antes de qualquer outra coisa. Além disso, você pode encontrar alguns conceitos básicos de este problema no meu papel e em suas referências.

Bellow eu forneci alguns comentários úteis que eu experimentei em meu trabalho:

  • Detecção de charset não é um processo infalível, porque é essencialmente baseada em dados estatísticos e o que realmente acontece é adivinhar não detectar
  • ICU4J é a principal ferramenta neste contexto pela IBM, imho
  • Ambos TikaEncodingDetector e Lucene-ICU4J estiver usando ICU4J e sua exatidão não tiveram uma diferença significativa a partir do qual o ICU4J em meus testes (no máximo 1%, como eu me lembro)
  • ICU4J é muito mais geral do que jchardet, ICU4J é apenas um pouco tendencioso para codificações família IBM enquanto jchardet é fortemente inclinado para utf-8
  • Devido ao uso generalizado de UTF-8 em HTML mundial; jchardet é uma escolha melhor do que ICU4J em geral, mas não é a melhor escolha!
  • ICU4J é ótimo para codificações específicas do Leste Asiático como EUC-KR, EUC-JP, SHIFT_JIS, BIG5 e as codificações família GB
  • Ambos ICU4J e jchardet são debacle em lidar com páginas HTML com o Windows-1251 e codificações do Windows-1256. O Windows-1251 aka CP1251 é amplamente utilizado para idiomas baseados em cirílico, como russo e Windows-1256 aka cp1256 é amplamente utilizado para árabe
  • Quase todos codificando ferramentas de detecção são usando métodos estatísticos, de modo que a precisão da saída depende fortemente do tamanho e o conteúdo da entrada
  • Algumas codificações são essencialmente os mesmos apenas com diferenças parciais, assim, em alguns casos, a codificação adivinhado ou detectado pode ser falso, mas ao mesmo tempo ser verdade! Como sobre o Windows-1252 e ISO-8859-1. (Referem-se ao último parágrafo, na seção de meu artigo 5.2)
Respondeu 12/05/2016 em 20:14
fonte usuário

votos
3

Qual biblioteca de usar?

Como desta escrita, são três bibliotecas que emergem:

Eu não incluem Apache Any23 porque usa ICU4J 3.4 sob o capô.

Como saber qual detectou a direita charset (ou o mais próximo possível)?

É impossível para certificar o charset detectados por cada bibliotecas acima. No entanto, é possível pedir-lhes por sua vez e marcar a resposta retornada.

Como marcar a resposta retornada?

Cada resposta pode ser atribuído um ponto. Quanto mais pontos uma resposta tem, mais confiança o charset detectado tem. Este é um método simples de pontuação. Você pode elaborar outros.

Existe algum código de exemplo?

Aqui está um trecho cheio implementação da estratégia descrito nas linhas anteriores.

public static String guessEncoding(InputStream input) throws IOException {
    // Load input data
    long count = 0;
    int n = 0, EOF = -1;
    byte[] buffer = new byte[4096];
    ByteArrayOutputStream output = new ByteArrayOutputStream();

    while ((EOF != (n = input.read(buffer))) && (count <= Integer.MAX_VALUE)) {
        output.write(buffer, 0, n);
        count += n;
    }

    if (count > Integer.MAX_VALUE) {
        throw new RuntimeException("Inputstream too large.");
    }

    byte[] data = output.toByteArray();

    // Detect encoding
    Map<String, int[]> encodingsScores = new HashMap<>();

    // * GuessEncoding
    updateEncodingsScores(encodingsScores, new CharsetToolkit(data).guessEncoding().displayName());

    // * ICU4j
    CharsetDetector charsetDetector = new CharsetDetector();
    charsetDetector.setText(data);
    charsetDetector.enableInputFilter(true);
    CharsetMatch cm = charsetDetector.detect();
    if (cm != null) {
        updateEncodingsScores(encodingsScores, cm.getName());
    }

    // * juniversalchardset
    UniversalDetector universalDetector = new UniversalDetector(null);
    universalDetector.handleData(data, 0, data.length);
    universalDetector.dataEnd();
    String encodingName = universalDetector.getDetectedCharset();
    if (encodingName != null) {
        updateEncodingsScores(encodingsScores, encodingName);
    }

    // Find winning encoding
    Map.Entry<String, int[]> maxEntry = null;
    for (Map.Entry<String, int[]> e : encodingsScores.entrySet()) {
        if (maxEntry == null || (e.getValue()[0] > maxEntry.getValue()[0])) {
            maxEntry = e;
        }
    }

    String winningEncoding = maxEntry.getKey();
    //dumpEncodingsScores(encodingsScores);
    return winningEncoding;
}

private static void updateEncodingsScores(Map<String, int[]> encodingsScores, String encoding) {
    String encodingName = encoding.toLowerCase();
    int[] encodingScore = encodingsScores.get(encodingName);

    if (encodingScore == null) {
        encodingsScores.put(encodingName, new int[] { 1 });
    } else {
        encodingScore[0]++;
    }
}    

private static void dumpEncodingsScores(Map<String, int[]> encodingsScores) {
    System.out.println(toString(encodingsScores));
}

private static String toString(Map<String, int[]> encodingsScores) {
    String GLUE = ", ";
    StringBuilder sb = new StringBuilder();

    for (Map.Entry<String, int[]> e : encodingsScores.entrySet()) {
        sb.append(e.getKey() + ":" + e.getValue()[0] + GLUE);
    }
    int len = sb.length();
    sb.delete(len - GLUE.length(), len);

    return "{ " + sb.toString() + " }";
}

Melhorias: O guessEncodingmétodo lê o inputstream inteiramente. Para grandes InputStreams esta pode ser uma preocupação. Todas essas bibliotecas iria ler todo o inputstream. Isto implicaria um grande consumo de tempo para detectar a charset.

É possível limitar o carregamento de dados inicial para alguns bytes e realizar a detecção de conjunto de caracteres de apenas os poucos bytes.

Respondeu 03/09/2015 em 10:38
fonte usuário

votos
3

Para arquivos ISO8859_1, não há uma maneira fácil de distingui-los de ASCII. Para arquivos Unicode no entanto pode-se geralmente detectar isso com base nos primeiros bytes do arquivo.

Arquivos UTF-8 e UTF-16 incluem um Order Byte Mark (BOM) no início do arquivo. O BOM é um espaço sem quebra de largura zero.

Infelizmente, por razões históricas, Java não detecta isso automaticamente. Programas como o Bloco de Notas irá verificar o BOM e usar a codificação apropriada. Usando UNIX ou Cygwin, você pode verificar a lista de materiais com o comando de arquivo. Por exemplo:

$ file sample2.sql 
sample2.sql: Unicode text, UTF-16, big-endian

Para Java, eu sugiro que você confira este código, que irá detectar os formatos de arquivos comuns e selecionar a codificação correta: Como ler um arquivo e especificar automaticamente a codificação correta

Respondeu 26/05/2009 em 08:20
fonte usuário

votos
1

Uma alternativa para TikaEncodingDetector é usar Tika AutoDetectReader .

Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
Respondeu 11/05/2015 em 13:04
fonte usuário

votos
0

Em Java simples:

final String[] encodings = { "US-ASCII", "ISO-8859-1", "UTF-8", "UTF-16BE", "UTF-16LE", "UTF-16" };

List<String> lines;

for (String encoding : encodings) {
    try {
        lines = Files.readAllLines(path, Charset.forName(encoding));
        for (String line : lines) {
            // do something...
        }
        break;
    } catch (IOException ioe) {
        System.out.println(encoding + " failed, trying next.");
    }
}

Esta abordagem irá tentar o codificações um por um, até que um trabalha ou corremos fora delas. (BTW minha lista de codificações tem apenas os itens, porque eles são as implementações charsets necessários em todas as plataformas Java, https://docs.oracle.com/javase/9/docs/api/java/nio/charset/Charset.html )

Respondeu 28/07/2018 em 16:59
fonte usuário

votos
-10

Você pode escolher o caractere apropriado definido no Construtor :

new InputStreamReader(new FileInputStream(in), "ISO8859_1");
Respondeu 31/01/2009 em 16:44
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more