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.
Ich habe diese Bibliothek, ähnlich wie jchardet, zur Erkennung der Codierung in Java verwendet: http://code.google.com/p/juniversalchardet/
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.
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()
}
Hier sind meine Favoriten:
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));
}
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);
}
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.
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.
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.
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.
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.
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
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.
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.
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:
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 .
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
Eine Alternative zu TikaEncodingDetector ist die Verwendung von Tika AutoDetectReader .
Charset charset = new AutoDetectReader(new FileInputStream(file)).getCharset();
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 )