Java: Jak określić poprawne kodowanie znaków strumienia

W odniesieniu do następującego wątku: Aplikacja Java: nie można poprawnie odczytać zakodowanego pliku iso-8859-1

Jaki jest najlepszy sposób, aby programowo określić poprawne kodowanie znaków strumienia wejściowego / pliku ?

Próbowałem użyć następujących:

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

Ale na pliku, który wiem, że jest zakodowany z iso8859_1 powyższy kod daje ASCII, co nie jest poprawne i nie pozwala mi poprawnie renderować zawartości pliku z powrotem do konsoli.

Author: Community, 2009-01-31

15 answers

Używałem tej biblioteki, podobnie jak jchardet do wykrywania kodowania w Javie: http://code.google.com/p/juniversalchardet/

 63
Author: Luciano Fiandesio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-19 13:44:36

Nie można określić kodowania dowolnego strumienia bajtów. Taka jest natura kodowania. Kodowanie oznacza odwzorowanie między wartością bajtu a jej reprezentacją. Więc każde kodowanie "może" być właściwe.

Metoda getEncoding() zwróci kodowanie, które zostało ustawione (przeczytaj JavaDoc) dla strumienia. To nie odgadnie kodowania dla Ciebie.

Niektóre strumienie mówią, które kodowanie zostało użyte do ich utworzenia: XML, HTML. Ale nie Dowolny bajt strumień.

W każdym razie, możesz spróbować odgadnąć kodowanie na własną rękę, jeśli musisz. Każdy język ma wspólną częstotliwość dla każdego znaku. W języku angielskim znak e pojawia się bardzo często, ale ê pojawia się bardzo rzadko. W strumieniu ISO-8859-1 zwykle nie ma znaków 0x00. Ale strumień UTF-16 ma ich dużo.

LUB: możesz zapytać użytkownika. Widziałem już aplikacje, które prezentują Ci fragment pliku w różnych kodowaniach i proszą cię o wybranie "poprawnego".

 92
Author: Eduard Wirch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-10 19:51:19

Zobacz też: http://site.icu-project.org / (icu4j) mają biblioteki do wykrywania charset z IOStream może być tak prosto:

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()
}
 31
Author: user345883,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-17 00:41:15

Oto moje ulubione:

TikaEncodingDetector

Zależność:

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

Próbka:

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

GuessEncoding

Zależność:

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

Próbka:

  public static Charset guessCharset2(File file) throws IOException {
    return CharsetToolkit.guessEncoding(file, 4096, StandardCharsets.UTF_8);
  }
 21
Author: Benny Neugebauer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-30 12:48:37

Możesz z pewnością zweryfikować plik dla określonego zestawu znaków poprzez dekodowanie za pomocą CharsetDecoder i uważaj na błędy" zniekształconego wejścia "lub" niezmontowalnego charakteru". Oczywiście, to mówi tylko, czy zestaw znaków jest zły; nie mówi, czy jest poprawny. Do tego potrzebujesz podstawy porównania, aby ocenić zdekodowane wyniki, np. czy wiesz wcześniej, czy znaki są ograniczone do jakiegoś podzbioru, czy tekst przylega do jakiegoś ścisłego formatu? Na najważniejsze jest to, że wykrywanie zestawów znaków jest domysłem bez żadnych gwarancji.

 12
Author: Zach Scrivena,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-01 07:44:18

Powyższe libs są prostymi detektorami BOM, które oczywiście działają tylko wtedy, gdy na początku pliku znajduje się BOM. Spójrz na http://jchardet.sourceforge.net / który skanuje tekst

 7
Author: Lorrat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-15 11:53:01

Znalazłem ładną bibliotekę stron trzecich, która może wykryć rzeczywiste kodowanie: http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding

Nie testowałem go intensywnie, ale wydaje się, że działa.

 5
Author: falcon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-07 09:04:04

Jeśli używasz ICU4J ( http://icu-project.org/apiref/icu4j/)

Oto Mój kod:

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

Pamiętaj, aby umieścić wszystkie spróbuj złapać go trzeba.

Mam nadzieję, że to ci pomoże.
 5
Author: ssamuel68,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-04 21:01:42

Jakiej biblioteki użyć?

W chwili pisania tego tekstu, są to trzy biblioteki, które pojawiają się:

Nie włączam Apache Any23 ponieważ używa ICU4j 3.4 pod maską.

Jak sprawdzić, który z nich wykrył właściwy kod (lub jak najbliżej)?

Nie jest możliwe poświadczenie zestawu znaków wykrytego przez każdą z powyższych bibliotek. Można jednak zapytać ich po kolei i zdobyć zwróconą odpowiedź.

Jak zdobyć zwróconą odpowiedź?

Każdej odpowiedzi można przypisać jeden punkt. Im więcej punktów ma odpowiedź, tym większe zaufanie ma wykryty zestaw znaków. Jest to prosta metoda punktacji. Możesz rozwinąć inne.

Czy Jest jakiś przykładowy kod?

Oto pełny fragment implementujący strategię opisaną w poprzednich wierszach.

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

ulepszenia: The guessEncoding metoda w całości odczytuje strumień wejściowy. W przypadku dużych strumieni wejściowych może to być problemem. Wszystkie te biblioteki odczytałyby cały strumień wejściowy. Oznaczałoby to duże zużycie czasu na wykrywanie zestawu znaków.

Możliwe jest ograniczenie początkowego ładowania danych do kilku bajtów i wykrycie zestawu znaków tylko na tych kilku bajtach.

 5
Author: Stephan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-31 07:58:52

Jeśli nie znasz kodowania swoich danych, nie jest to łatwe do ustalenia, ale możesz spróbować użyć biblioteki, aby odgadnąć to . Istnieje również podobne pytanie .

 4
Author: Fabian Steeg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:03:03

Z tego co wiem, nie ma w tym kontekście biblioteki ogólnej, która byłaby odpowiednia dla wszystkich rodzajów problemów. Tak więc, dla każdego problemu powinieneś przetestować istniejące biblioteki i wybrać najlepszą, która spełnia ograniczenia Twojego problemu, ale często żadna z nich nie jest odpowiednia. W takich przypadkach możesz napisać własny Detektor kodowania! Tak jak pisałem ...

Napisałem narzędzie meta java do wykrywania kodowania znaków stron HTML, używając IBM ICU4j i Mozilla JCharDet jako wbudowanego komponenty. Tutaj znajdziesz moje narzędzie, przeczytaj sekcję README przed czymkolwiek innym. Podstawowe pojęcia tego problemu znajdziecie również w moim artykule oraz w jego referencjach.

Poniżej podałem kilka przydatnych komentarzy, których doświadczyłem w swojej pracy:

  • wykrywanie znaków nie jest niezawodnym procesem, ponieważ opiera się zasadniczo na danych statystycznych, a to, co się dzieje, to zgadywanie nie wykrywanie
  • Icu4j jest głównym narzędziem w tym kontekście IBM, imho]}
  • zarówno TikaEncodingDetector, jak i Lucene-ICU4j używają icu4j, a ich dokładność nie miała istotnej różnicy, od której icu4j w moich testach (co najwyżej %1, o ile pamiętam)
  • Icu4j jest znacznie bardziej ogólny niż jchardet, icu4j jest tylko trochę stronniczy w stosunku do kodowania rodziny IBM, podczas gdy jchardet jest mocno stronniczy w stosunku do utf-8.]}
  • ze względu na powszechne użycie UTF-8 W HTML-world; jchardet jest lepszy wybór niż icu4j w ogóle, ale nie jest najlepszym wyborem!
  • Icu4j jest świetny do kodowania specyficznych dla Azji Wschodniej, takich jak EUC-KR, EUC-JP, SHIFT_JIS, BIG5 i kodowania rodziny GB]}
  • icu4j i jchardet są klęską w radzeniu sobie ze stronami HTML z kodowaniem Windows-1251 i Windows-1256. Windows-1251 aka cp1251 jest szeroko stosowany w językach opartych na cyrylicy, takich jak Rosyjski i Windows-1256 aka cp1256 jest szeroko stosowany w języku arabskim
  • prawie wszystkie narzędzia do wykrywania kodowania są za pomocą metod statystycznych, więc Dokładność wyjścia silnie zależy od wielkości i zawartości wejścia
  • niektóre kodowania są zasadniczo takie same tylko z częściowymi różnicami, więc w niektórych przypadkach domyślne lub wykryte kodowanie może być fałszywe, ale jednocześnie prawdziwe! Co do Windows-1252 i ISO-8859-1. (Zobacz ostatni akapit w sekcji 5.2 mojej pracy)
 3
Author: faghani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-08 20:29:08

Dla plików ISO8859_1 nie ma łatwego sposobu odróżnienia ich od ASCII. W przypadku plików Unicode można jednak zazwyczaj wykryć to na podstawie kilku pierwszych bajtów pliku.

Pliki

UTF-8 i UTF-16 zawierają znak Byte Order Mark (BOM) na samym początku pliku. BOM jest przestrzenią o zerowej szerokości.

Niestety, ze względów historycznych, Java nie wykrywa tego automatycznie. Programy takie jak Notatnik sprawdzą BOM i użyją odpowiedniego kodowanie. Używając unix lub Cygwin, możesz sprawdzić BOM za pomocą polecenia file. Na przykład:

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

W przypadku Javy proponuję sprawdzić ten kod, który wykryje popularne formaty plików i wybierze poprawne kodowanie: Jak odczytać plik i automatycznie określić poprawne kodowanie

 2
Author: brianegge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-10 01:03:18

Alternatywą dla TikaEncodingDetector jest użycie Tika AutoDetectReader .

Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
 1
Author: Nolf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-03 09:47:17

W języku Java:

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

To podejście będzie próbować kodowania jeden po drugim, dopóki jeden nie zadziała lub skończy się. (BTW Moja lista kodowań ma tylko te pozycje, ponieważ są to implementacje znaków wymagane na każdej platformie Java, https://docs.oracle.com/javase/9/docs/api/java/nio/charset/Charset.html )

 0
Author: Andres,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-30 14:12:56

Czy możesz wybrać odpowiedni zestaw znaków w Konstruktorze :

new InputStreamReader(new FileInputStream(in), "ISO8859_1");
 -11
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-31 15:44:08