it-swarm.com.de

Java: So ermitteln Sie die korrekte Zeichensatzkodierung eines Streams

In Bezug auf den folgenden Thread: Java-App: Iso-8859-1-codierte Datei kann nicht richtig gelesen werden.

Was ist der beste Weg, um programmgesteuert die korrekte Zeichensatzkodierung eines Eingabestroms/einer Datei zu ermitteln?

Ich habe folgendes versucht:

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

Bei einer Datei, von der ich weiß, dass sie mit ISO8859_1 codiert wird, ergibt der obige Code ASCII, was nicht korrekt ist, und erlaubt mir nicht, den Inhalt der Datei korrekt auf der Konsole wiederzugeben.

120
Joel

Ich habe diese Bibliothek, ähnlich wie jchardet, zur Erkennung der Codierung in Java verwendet: http://code.google.com/p/juniversalchardet/

65

Sie können die Codierung eines beliebigen Byte-Streams nicht bestimmen. Dies ist die Art der Kodierungen. Eine Kodierung bedeutet eine Zuordnung zwischen einem Byte-Wert und seiner Darstellung. Also könnte jede Kodierung "richtig" sein.

Die getEncoding () - Methode gibt die für den Stream eingerichtete Codierung zurück (lesen Sie die JavaDoc ). Die Kodierung wird für Sie nicht erraten.

Einige Streams sagen Ihnen, welche Kodierung für ihre Erstellung verwendet wurde: XML, HTML. Aber kein beliebiger Byte-Stream.

Auf jeden Fall könnten Sie versuchen, selbst eine Kodierung zu erraten, wenn Sie müssen. Jede Sprache hat für jedes Zeichen eine gemeinsame Frequenz. Im Englischen erscheint das Zeichen sehr oft, aber ê erscheint sehr selten. In einem ISO-8859-1-Stream gibt es normalerweise keine 0x00-Zeichen. Aber ein UTF-16-Stream hat viele davon.

Oder: Sie könnten den Benutzer fragen. Ich habe bereits Anwendungen gesehen, die Ihnen einen Ausschnitt der Datei in verschiedenen Kodierungen zeigen und Sie bitten, das "richtige" auszuwählen.

95
Eduard Wirch

check this out: http://site.icu-project.org/ (icu4j) Sie haben Bibliotheken zum Erkennen des Zeichensatzes von IOStream könnte so einfach sein:

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()
}
32
user345883

Hier sind meine Favoriten:

TikaEncodingDetector

Abhängigkeit:

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

Probe:

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

GuessEncoding

Abhängigkeit:

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

Probe:

  public static Charset guessCharset2(File file) throws IOException {
    return CharsetToolkit.guessEncoding(file, 4096, StandardCharsets.UTF_8);
  }
23

Sie können die Datei für einen bestimmten Zeichensatz sicher validieren , indem Sie decodieren mit einem ) versehen + CharsetDecoder und auf "fehlerhafte Eingabe" oder "nicht abbildbare Zeichen" -Fehler achten. Dies sagt Ihnen natürlich nur, ob ein Zeichensatz falsch ist; es sagt dir nicht, ob es richtig ist. Dafür benötigen Sie eine Vergleichsbasis, um die decodierten Ergebnisse auszuwerten, z. Wissen Sie vorher, ob die Zeichen auf eine bestimmte Teilmenge beschränkt sind oder ob der Text einem bestimmten strengen Format entspricht? Das Fazit ist, dass die Zeichensatzerkennung ohne Garantie funktioniert.

13
Zach Scrivena

Welche Bibliothek soll verwendet werden?

Zum Zeitpunkt des Schreibens handelt es sich um drei Bibliotheken, die auftauchen: 

Apache Any23 füge ich nicht hinzu, weil ICU4j 3.4 unter der Haube verwendet wird.

Wie kann man feststellen, welcher der right - Zeichensatz (oder so nah wie möglich) erkannt hat?

Es ist nicht möglich, den Zeichensatz zu bestätigen, der von jeder der obigen Bibliotheken erkannt wird. Es ist jedoch möglich, sie nacheinander zu fragen und die zurückgegebene Antwort zu bewerten.

Wie bewerte ich die Antwort?

Jeder Antwort kann ein Punkt zugewiesen werden. Je mehr Punkte eine Antwort hat, desto mehr Vertrauen hat der erkannte Zeichensatz. Dies ist eine einfache Bewertungsmethode. Sie können andere ausarbeiten.

Gibt es einen Beispielcode?

Hier ist ein vollständiger Ausschnitt, der die in den vorherigen Zeilen beschriebene Strategie implementiert.

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

Verbesserungen: Die guessEncoding-Methode liest den Eingabestrom vollständig. Für große Eingangsströme kann dies ein Problem sein. Alle diese Bibliotheken würden den gesamten Eingabestrom lesen. Dies würde einen großen Zeitaufwand für die Erkennung des Zeichensatzes bedeuten.

Es ist möglich, das anfängliche Laden von Daten auf einige Bytes zu beschränken und die Zeichensatzerkennung nur für diese wenigen Bytes durchzuführen.

9
Stephan

Die oben genannten Bibliotheken sind einfache Stücklistendetektoren, die natürlich nur funktionieren, wenn sich am Anfang der Datei eine Stückliste befindet. Werfen Sie einen Blick auf http://jchardet.sourceforge.net/ , das den Text durchsucht 

7
Lorrat

Wenn Sie ICU4J verwenden ( http://icu-project.org/apiref/icu4j/ )

Hier ist mein Code:

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

Denken Sie daran, alle Versuchssperren zu verwenden.

Ich hoffe das funktioniert für dich.

5
ssamuel68

Ich habe eine Bibliothek von Nice Third Party gefunden, die die tatsächliche Kodierung erkennen kann: http://glaforge.free.fr/wiki/index.php?wiki=GuessEncoding

Ich habe es nicht ausgiebig getestet, aber es scheint zu funktionieren.

5
falcon

Soweit ich weiß, gibt es in diesem Zusammenhang keine allgemeine Bibliothek, die für alle Arten von Problemen geeignet ist. Daher sollten Sie für jedes Problem die vorhandenen Bibliotheken testen und die beste auswählen, die den Einschränkungen Ihres Problems entspricht, aber oft ist keine davon angemessen. In diesen Fällen können Sie Ihren eigenen Encoding Detector schreiben! Wie ich geschrieben habe ...

Ich habe ein Meta-Java-Tool zum Erkennen der Zeichensatzkodierung von HTML-Webseiten geschrieben, wobei IBM ICU4j und Mozilla JCharDet als integrierte Komponenten verwendet werden. Hier Sie finden mein Werkzeug. Lesen Sie bitte den Abschnitt README, bevor Sie etwas anderes tun. Einige grundlegende Konzepte dieses Problems finden Sie auch in meinem paper und in seinen Referenzen. 

Unten habe ich einige hilfreiche Kommentare gegeben, die ich in meiner Arbeit erfahren habe: 

  • Die Erkennung von Zeichensätzen ist kein narrensicherer Prozess, da sie im Wesentlichen auf statistischen Daten basiert und was tatsächlich passiert, ist raten nicht erkennen
  • icu4j ist in diesem Zusammenhang das wichtigste Werkzeug von IBM, imho
  • Sowohl TikaEncodingDetector als auch Lucene-ICU4j verwenden icu4j und ihre Genauigkeit hatte keinen bedeutsamen Unterschied zu denen der icu4j in meinen Tests (höchstens% 1, wie ich mich erinnere).
  • icu4j ist viel allgemeiner als jchardet, icu4j ist nur ein bisschen zu den Kodierungen der IBM-Familie geneigt, während jchardet stark zu utf-8 neigt
  • Aufgrund der weit verbreiteten Verwendung von UTF-8 in der HTML-Welt; jchardet ist insgesamt eine bessere Wahl als icu4j, ist aber nicht die beste Wahl!
  • icu4j eignet sich hervorragend für ostasiatische Codierungen wie EUC-KR, EUC-JP, SHIFT_JIS, BIG5 und die GB-Codierung
  • Icu4j und jchardet sind ein Debakel im Umgang mit HTML-Seiten mit Windows-1251- und Windows-1256-Kodierungen. Windows-1251 alias cp1251 wird häufig für kyrillische Sprachen wie Russisch verwendet und Windows-1256 alias cp1256 wird häufig für Arabisch verwendet 
  • Nahezu alle Codierungserkennungswerkzeuge verwenden statistische Methoden. Daher hängt die Genauigkeit der Ausgabe stark von der Größe und dem Inhalt der Eingabe ab 
  • Einige Kodierungen sind im Wesentlichen die gleichen, nur mit partiellen Unterschieden, daher kann die erratene oder erkannte Kodierung in manchen Fällen falsch sein, gleichzeitig aber wahr sein! Wie etwa Windows-1252 und ISO-8859-1. (Siehe den letzten Absatz unter Abschnitt 5.2 meines Papiers.)
4
faghani

Wenn Sie die Kodierung Ihrer Daten nicht kennen, ist es nicht so einfach zu bestimmen, aber Sie könnten versuchen, eine Bibliothek zu verwenden, um sie zu erraten . Es gibt auch eine ähnliche Frage .

4
Fabian Steeg

Bei ISO8859_1-Dateien gibt es keine einfache Möglichkeit, sie von ASCII zu unterscheiden. Bei Unicode-Dateien kann man dies jedoch generell anhand der ersten Bytes der Datei erkennen.

UTF-8- und UTF-16-Dateien enthalten eine Byte Order Mark (BOM) ganz am Anfang der Datei. Die Stückliste ist ein Nullraum ohne Bruch. 

Aus historischen Gründen erkennt Java dies leider nicht automatisch. Programme wie Notepad prüfen die Stückliste und verwenden die entsprechende Kodierung. Mit Unix oder Cygwin können Sie die Stückliste mit dem Dateibefehl überprüfen. Zum Beispiel:

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

Für Java empfehle ich Ihnen, diesen Code auszulesen, der die gängigen Dateiformate erkennt und die richtige Kodierung auswählt: Wie liest man eine Datei und gibt automatisch die richtige Kodierung an

2
brianegge

Eine Alternative zu TikaEncodingDetector ist die Verwendung von Tika AutoDetectReader .

Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
1
Nolf

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

Bei diesem Ansatz werden die Kodierungen nacheinander getestet, bis eine funktioniert oder wir davon ausgehen. (Übrigens, meine Kodierliste enthält nur diese Elemente, da es sich um die auf jeder Java-Plattform erforderlichen Zeichensatzimplementierungen handelt, https: // docs .Oracle.com/javase/9/docs/api/Java/nio/Zeichensatz/Zeichensatz.html )

0
Andres