Text-Encoding, Codepage, Charset

+ andere TechDocs
+ Zeichenkodierung
+ ASCII-Tabelle
+


Inhalt

  1. Beispiele für Text-Encodings (Codepages, Charsets)
  2. Verschiedene Kodierungen, beispielsweise für das Euro-Symbol
  3. Hinweise, Beispiele und Einstellmöglichkeiten
  4. Java-Codeschnipsel zu UTF-8
  5. Setzen der Codepage für die Konsolenausgabe mit Java
  6. Raten des Encodings mit Java


Beispiele für Text-Encodings (Codepages, Charsets)

EncodingVerwendung äöüߧ ½²
ASCII
= US-ASCII
= ANSI X3.4-1986
8-Bit-Zeichensatz, der aber nur 7 Bits verwendet und somit nur 128 Zeichen enthält (33 Steuerzeichen und 95 druckbare Zeichen). ----------
Codepage 437
= CP437
= DOS-US
= OEM-US
In USA im Windows-Kommandozeilenfenster (= DOS-Box) verwendeter 8-Bit-Zeichensatz. 849481E1--ABFDFB-
Codepage 850
= CP850
= DOS-Latin-1
In westeuropäischen Ländern im Windows-Kommandozeilenfenster (= DOS-Box) verwendeter 8-Bit-Zeichensatz. 849481E1F5-ABFD--
Cp1252
= Windows-1252
(manchmal auch
ANSI genannt)
Windows-Standardzeichensatz für westeuropäische Länder (8 Bit).
Cp1252 enthält alle Zeichen aus ISO 8859-1 und ISO 8859-15, allerdings teilweise mit anderer Kodierung.
E4F6FCDFA780BDB2--
ISO 8859-1
= Latin-1
Häufig in HTML-Seiten verwendeter 8-Bit-Zeichensatz, inkl. äöüß, aber ohne €.
Siehe auch den Vergleich 8859-1 / 8859-15 / 1252 / Unicode.
E4F6FCDFA7-BDB2--
ISO 8859-15
= Latin-9
Ähnlich wie ISO 8859-1, aber enthält € und zusätzliche französische Sonderzeichen. E4F6FCDFA7A4-B2--
UTF-8
Die am weitesten verbreitete Kodierung für Unicode-Zeichen. Wird in einigen Betriebssystemen (GNU/Linux, Unix) und teilweise in verschiedenen Internetdiensten (E-Mail, Web) verwendet. Zeichen werden in verschiedener Länge von 1 bis 4 Bytes kodiert. C3A4C3B6C3BCC39FC2A7E282ACC2BDC2B2E2889AE28891
UTF-16
(manchmal auch
mit Unicode
gemeint)
Wird in einigen Betriebssystemen (Windows, OS X) und Programmiersprachen (Java, .NET) für die interne Zeichendarstellung verwendet. Zeichen werden entweder in 2 oder in 4 Bytes kodiert. Siehe auch Suchseiten isthisthingon-unicode und fileformat-unicode. 00E400F600FC00DF00A720AC00BD00B2221A2211
EBCDIC 273
Hauptsächlich auf IBM-Großrechnern (AS/400, iSeries) verwendeter 8-Bit-Zeichensatz. C06AD0A17C-B8EA--
HTML-Entitäten "Named entities" für nicht-ASCII-Zeichen in HTML-Sourcecode (aber nicht für XHTML und nicht für XML). äöüߧ€ ½²√∑

Die Felder in den ä/ö/ü/ß/§/€/½/²/√/∑-Spalten enthalten den Zeichencode in Hexadezimaldarstellung.



Verschiedene Kodierungen, beispielsweise für das Euro-Symbol

Zu vielen Zeichen gibt es mehrere Möglichkeiten zur Kodierung in UTF-8. Zum Beispiel das Euro-Zeichen ('€') kann sowohl als Zwei-Byte-Code "0xC2, 0x80" als auch als Drei-Byte-Code "0xE2, 0x82, 0xAC" kodiert werden.

Siehe hierzu:
http://www.fileformat.info/info/unicode/char/0080/index.htm (unter "Java Data") und
http://www.fileformat.info/info/unicode/char/20ac/index.htm.

Ähnliches gilt auch bei der Kodierung für HTML-Seiten: In den meisten Fällen werden sowohl '€' (entspricht 'C2 80') als auch '€' (entspricht 'E2 82 AC') als '€' dargestellt.

HTML und XML unterstützen Unicode mit Zeichencodes, die unabhängig vom eingestellten Zeichensatz das Unicode-Zeichen darstellen. Die Notation lautet � für dezimale Notation bzw. � für hexadezimale Notation, wobei das 0000 die Unicode-Nummer des Zeichens darstellt.

Zwischen HTML und XML gibt es wichtige Unterschiede, wie das folgende Beispiel demonstriert:

Legen Sie folgende Euro-Encoding.html an:

<html><body>
<h3> Euro-Encoding.html </h3>
&amp;#x20AC; = &#x20AC; <br/>
&amp;#x80;   = &#x80; <br/>
&amp;euro;   = &euro; <br/>
&apos; &#xE4; &#xF6; &#xFC; &#xDF; &#xA7; &#xBD; &#xB2; &#x20AC; &#x221A; &#x2211; <br/>
</body></html>

Legen Sie folgende Euro-Encoding.xml an:

<!-- Euro-Encoding.xml -->
<x><e-20AC> &amp;#x20AC; = &#x20AC; </e-20AC>
   <e-0080> &amp;#x80;   = &#x80; </e-0080>
   <e-euro> &amp;euro;   = Nicht definierte Entit&#xE4;t </e-euro>
   <Sonderzeichen> &apos; &#xE4; &#xF6; &#xFC; &#xDF; &#xA7; &#xBD; &#xB2; &#x20AC; &#x221A; &#x2211; </Sonderzeichen>
</x>

Laden Sie beide Dateien in einen Webbrowser: In der HTML-Datei funktionieren unter Windows im Mozilla Firefox 3.5 und Microsoft Internet Explorer 8 alle drei Kodierungen des €-Zeichens (&#x20AC;, &#x80; und &euro;). In der XML-Datei funktioniert nur &#x20AC;. Bei &#x80; wird in den meisten Browsern nichts angezeigt und &euro; würde sogar zu der Fehlermeldung "Nicht definierte Entität" führen.
In XML gibt es nur fünf Entitäten:

&amp; &
&lt; <
&gt; >
&quot; "
&apos; '

Einen weiteren Unterschied gibt es mit der (in den Beispielen in den Sonderzeichen enthaltenen) &apos;-Entity-Referenz für das Apostroph ('): Während dies in XML-Dateien korrekt erkannt wird, zeigt es der Mozilla Firefox 3.5 auch in der HTML-Datei korrekt an, aber der Microsoft Internet Explorer 8 nicht.

Unter Windows kann in einigen Programmen (in RichEdit-Feldern) der Zeichencode dezimal als Alt+<dezimales Unicode mit führender 0> auf dem numerischen Tastaturfeld eingegeben werden (also z.B. bei gedrückter Alt-Taste 0228 für ä). Ab Microsoft Word 2002 kann Unicode auch hexadezimal eingegeben werden, indem im Dokument <Unicode> oder U+<Unicode> eingetippt wird und anschließend die Tastenkombination Alt+C im Dokument bzw. Alt+X in Dialogfeldern gedrückt wird (also z.B. Leerzeichen, 00E4 und Alt+C für ä). Diese Tastenkombination kann auch benutzt werden, um den Code des vor dem Cursor stehenden Zeichens anzuzeigen.



Hinweise, Beispiele und Einstellmöglichkeiten

SoftwareHinweise, Beispiele und Einstellmöglichkeiten
Windows Cp1252 / Codepage 850:
Der Windows-Standardzeichensatz für westeuropäische Länder ist der 8-Bit-Zeichensatz Cp1252 (obwohl Windows intern UTF-16 verwendet). Im Kommandozeilenfenster (= DOS-Box) verwendet Windows normalerweise einen anderen Zeichensatz (in Westeuropa CP850).
chcp.com:
Das im Windows-Kommandozeilenfenster ausführbare Kommando CHCP zeigt die aktive Codepage des Kommandozeilenfensters an (normalerweise 850). CHCP ermöglicht auch die Umschaltung auf eine andere Codepage (z.B. "
chcp 1252"). Allerdings funktioniert das nur, wenn Sie in den Eigenschaften des Kommandozeilenfensters eine andere Schriftart als die defaultmäßige "Rasterschriftart" wählen, also entweder "Consolas" oder "Lucida Console".
charmap.exe:
Zeichentabelle-Anzeigeprogramm unter Windows.
CodePage-Einstellungen in der Windows-Registry:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage
Java Infos zum Text-Encoding in Java:
Converting Text, Lexical Structure und Supported Encodings.
Beispiele für Java-Klassen, bei denen Text-Encoding eine Rolle spielt:
Charset, CharsetEncoder, String, String.getBytes(), InputStreamReader, InputStreamReader.getEncoding(), OutputStreamWriter, OutputStreamWriter.getEncoding(), Properties.load(), Properties.store(), ServletResponse.setContentType(), ServletResponse.setCharacterEncoding().
Java verwendet intern UTF-16. Aber die Properties-Klasse liest und speichert Properties-Dateien in ISO-8859-1 (Sonderzeichen müssen als Unicode-Escape-Sequenz "\uXXXX" eingetragen sein).
native2ascii.exe:
Der Native-to-ASCII Converter (im JDK-bin-Verzeichnis) kann das Character Encoding von Properties-Dateien konvertieren.
JVM Als JVM-Kommandozeilenparameter beispielsweise:
-Duser.language=de -Duser.region=DE -Dfile.encoding=ISO-8859-1
Maven
(pom.xml)
Die erste Zeile (XML Processing Instruction) der pom.xml definiert das Encoding dieser XML-Datei und sollte UTF-8 definieren:
<?xml version="1.0" encoding="UTF-8"?>
Zusätzlich sollte das Encoding der Sourcecode-Dateien angegeben werden, welches häufig nicht UTF-8, sondern zum Beispiel ISO-8859-1 ist. Dies muss eventuell an mehreren Stellen erfolgen, beispielsweise:
<properties>
  <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<reporting>
  ...<sourceEncoding>ISO-8859-1</sourceEncoding>
Siehe auch Maven Site Reports und POM Element for Source File Encoding.
Eclipse Die Defaulteinstellung ist unter Windows Cp1252 und unter Linux UTF-8, weshalb es leicht zu Problemen kommen kann. Falls Programmierer mit verschiedenen Betriebssystemen arbeiten, sollte als "reduzierter Kompromiss" ISO-8859-1 eingestellt werden. Sonderzeichen können als Unicode-Escape-Sequenz "\uXXXX" eingetragen werden.
Globale Einstellung:
'Window' | 'Preferences' | 'General' | 'Workspace': "Text file encoding":
Projektspezifische Einstellung:
Im Project Explorer mit rechter Maustaste auf Projekt, 'Properties' | 'Resource': "Text file encoding".
XML Erste Zeile von XML-Dateien, beispielsweise:
<?xml version="1.0" encoding="UTF-8"?>
Einige Tools behandeln XML-Dateien immer als UTF-8-kodiert, selbst dann, wenn ein anderes Encoding angegeben ist.
MySQL Geben Sie das gewünschte Encoding vor, beispielsweise in der mysql.ini oder als Postfix zur DriverManager-Connection-URL und überprüfen Sie die eingestellten Werte:
default-character-set=utf8
?useUnicode=true&characterEncoding=UTF-8
Show Variables;
character_set_connection = utf8
Sehen Sie sich an: UTF-8 mit MySQL.
Oracle-DB Prüfen Sie die länderspezifischen DB-Einstellungen:
Select * from nls_database_parameters;
NLS_CHARACTERSET: AL32UTF8

Wenn Sie als Client SqlPlus verwenden, muss die NLS_LANG-Variable korrekt gesetzt sein.
Sehen Sie sich an: Probleme mit UTF-8 unter Oracle.
IBM DB2/400
(AS/400, iSeries)
Häufig EBCDIC mit Cp273. Siehe auch:
Select CCSID from SysColumns;
DB2CODEPAGE
HTTP Beispielsweise bei Anfrage:
Accept-Charset: ISO-8859-1
Beispielsweise beim Ergebnis im HTTP-Header:
Content-Type: text/html; charset=ISO-8859-1
HTML Einstellung im HTML-Header-Sourcecode (funktioniert nur, wenn nicht charset im HTTP-Header gesetzt ist), beispielsweise:
<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" /></head>
Java Servlet Java Servlet, beispielsweise:
HttpServletResponse.setContentType( "text/xml; charset=UTF-8" )
HttpServletResponse.setCharacterEncoding( "UTF-8" )
JSP Einstellung im JSP-Sourcecode, beispielsweise:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" %>
REST Content-Type: ...; charset=...
Z.B. für XML per JAX-RS:
WebResource.type("text/xml; charset=utf-8").accept("text/xml; charset=utf-8").post(...);
E-Mail E-Mail-Versand mit Apache Commons Email:
MultiPartEmail.setCharset( "UTF-8" );
MultiPartEmail.attach( new ByteArrayDataSource( anhangInputStream, "text/xml; charset=UTF-8" ), anhangDateiName, anhangBeschreibung, EmailAttachment.ATTACHMENT );
Oracle WebLogic In der weblogic.xml wird der Character Set für GET- und POST-Daten in HTTP-Requests voreingestellt, beispielsweise so:
...<charset-params><input-charset>
   <java-charset-name>UTF-8</java-charset-name>...

Siehe auch: weblogic.xml Deployment Descriptor Elements.
Apache HTTP Server Konfigurationsdatei httpd.conf (für text/html und text/plain):
AddDefaultCharset ISO-8859-1
etc. Es gibt natürlich noch viele weitere Möglichkeiten für Encoding-Fehlinterpretationen, beispielsweise SQL-Skripte, JMS-Messagetexte, in der URL enthaltene HTTP-GET- und REST-Parameter, ...


Java-Codeschnipsel zu UTF-8

Bei Dateien im UTF-Format wird oft ein BOM (Byte Order Mark)-Prefix vorangestellt, mit dem die Byte-Reihenfolge (LE/Little-Endian oder BE/Big-Endian) unterschieden werden kann. Dies kann allerdings in einigen Anwendungen zu Problemen führen. In einigen Editoren können Sie die BOM-Behandlung konfigurieren, siehe zum Beispiel in UltraEdit.

Falls Sie Textdateien im UTF-Format weiter per Java verarbeiten wollen, müssen Sie eventuell in der ersten Zeile das erste Zeichen entfernen, falls dieses eine BOM ist, zum Beispiel so:

   // BOM entfernen:
   if( firstLine.startsWith( "\uFEFF" ) ) { firstLine = firstLine.substring( 1 ); }

Die Länge eines UTF-8-kodierten Textstrings in Anzahl Bytes kann folgendermaßen ermittelt werden:

  meinString.getBytes( "UTF-8" ).length

Um einen Textstring für eine maximale Länge in Bytes zu begrenzen (oder aufzuteilen), muss die maximal mögliche Zeichenanzahl bis zu dieser Byte-Grenze ermittelt werden. Dabei muss beachtet werden, dass die Grenze nicht mitten in einem UTF-8-Drei-Byte-Code verlaufen darf, sondern nur zwischen ganzen Zeichen.

  /**
   * Findet fuer Strings fuer eine gegebene maximale Laenge in Bytes die maximale Laenge in Anzahl Characters.
   * @param str           Der zu untersuchende String
   * @param charsetName   Name des Charactersets (z.B. "UTF-8")
   * @param maxByteLength Maximale Laenge in Bytes (z.B. 4000 fuer Oracle-DB)
   * @return              Maximale Laenge in Anzahl Characters
   */
  public static int findMaxUtfCharLength( String str, String charsetName, int maxByteLength )
  {
    try {
      byte[] b = str.getBytes( charsetName );
      if( b.length <= maxByteLength ) return str.length();
      while( (0 < maxByteLength) && (((byte) (b[maxByteLength] & 0xC0)) == (byte) 0x80) )  maxByteLength--;
      return (new String( b, 0, maxByteLength, charsetName )).length();
    } catch( UnsupportedEncodingException ex ) {
      throw new RuntimeException( "UnsupportedEncodingException with " + charsetName, ex );
    }
  }


Setzen der Codepage für die Konsolenausgabe mit Java

Wenn Sie unter Windows im Kommandozeilenfenster (DOS-Box) in Java-Programmen den Text "äöüߧ€½²√∑" einfach mit System.out.println() ausgeben, erhalten Sie "õ÷³¯ºÇ¢¦??" statt des gewünschten Textes.

Um die korrekte Ausgabe zu erhalten, müssen Sie die Codepage umschalten. Leider gibt es in Java keinen direkten Weg, um die Codepage des Kommandozeilenfensters zu erfragen. Deshalb muss die korrekte Codepage entweder geraten werden (im unten gezeigten ConsoleEncoding-Programm über System.getProperty( "os.name" )) oder die gewünschte Codepage wird übergeben (im ConsoleEncoding-Programm über System.getProperty( "console.encoding" )).

Zum Setzen der Codepage gibt es mehrere Möglichkeiten:

  1. Sie können die System-Property "file.encoding" auf die gewünschte Codepage setzen, zum Beispiel für das ConsoleEncoding-Programm so:

    java -Dfile.encoding=CP850 ConsoleEncoding

    Dann sind alle drei Ausgaben von ConsoleEncoding korrekt (außer den €/√/∑-Zeichen, die es in CP850 nicht gibt). Der Nachteil ist, dass jetzt auch Dateioperationen mit dieser Codepage erfolgen, was normalerweise nicht gewünscht ist.

  2. Besser ist normalerweise, wenn nur die Codepage der Textausgabe im Kommandozeilenfenster umgeschaltet wird. Hierzu gibt es auch mehrere Möglichkeiten. Bei der ersten Möglichkeit wird ein PrintWriter mit der gewünschten Codepage erzeugt, über den die Ausgabe erfolgt:

    PrintWriter pw = new PrintWriter( new OutputStreamWriter( System.out, "<MeineCodepage>" ) );

  3. Alternativ kann der Standard-Output-Stream umgelenkt werden über:

    System.setOut( new PrintStream( System.out, true, "<MeineCodepage>" ) );

    Dann wird bei allen folgenden Ausgaben mit System.out.println() die gesetzte Codepage verwendet.

Testen Sie die beiden letzten Optionen mit folgendem ConsoleEncoding-Programm (für die Sonderzeichen: "ä ö ü ß § € ½ ² √ ∑", oder übergeben Sie andere Sonderzeichen als Kommandozeilenparameter):

javac ConsoleEncoding.java

java ConsoleEncoding [im Windows-Kommandozeilenfenster]
java -Dconsole.encoding=CP850 ConsoleEncoding
java -Dconsole.encoding=Cp1252 ConsoleEncoding [in Eclipse-Konsole]
java ConsoleEncoding [unter Linux]

import java.io.*;
import java.nio.charset.Charset;

/**
 * Aufrufbeispiel fuer Linux:
 *    java ConsoleEncoding
 * Aufrufbeispiel fuer Windows in Westeuropa:
 *    java -Dconsole.encoding=CP850 ConsoleEncoding
 * Aufrufbeispiel innerhalb Eclipse unter Windows:
 *    java -Dconsole.encoding=Cp1252 ConsoleEncoding
 */
public class ConsoleEncoding
{
   public static void main( String[] args ) throws IOException
   {
      // Sonderzeichen: &auml; &ouml; &uuml; &szlig; &sect; &euro; &frac12; &sup2; &radic; &sum;
      String sonderzeichen = ( args.length > 0 ) ? args[0] :
                             "\u00E4\u00F6\u00FC\u00DF\u00A7\u20AC\u00BD\u00B2\u221A\u2211";
      String osn  = System.getProperty( "os.name" );
      String fcp  = System.getProperty( "file.encoding" );
      String ccp  = System.getProperty( "console.encoding" );
      String ccps = ccp + " (uebergeben)";

      if( ccp == null ) {
         // Wir raten die Codepage der Konsole
         // (Cp850 ist nur fuer westeuropaeische Laender korrekt):
         ccp = ( osn != null && osn.contains( "Windows" ) ) ? "Cp850" : fcp;
         ccps = ccp + " (vermutet)";
      }

      System.out.println( "os.name:          " + osn );
      System.out.println( "default-charset:  " + Charset.defaultCharset() );
      System.out.println( "file.encoding:    " + fcp );
      System.out.println( "console.encoding: " + ccps );

      // Ausgabe mit Standard-System.out und unveraenderter Codepage:
      System.out.println( "Mit System.out:   " + sonderzeichen );

      // Ausgabe mit PrintWriter und gesetzter Konsolen-Codepage:
      PrintWriter pw = new PrintWriter( new OutputStreamWriter( System.out, ccp ) );
      pw.println( "Mit PrintWriter:  " + sonderzeichen );
      pw.flush();

      // System.out-Ausgabe mit neuem PrintStream mit gesetzter Konsolen-Codepage:
      System.setOut( new PrintStream( System.out, true, ccp ) );
      System.setErr( new PrintStream( System.err, true, ccp ) );
      System.out.println( "Mit PrintStream:  " + sonderzeichen );
   }
}

Sie erhalten:

Windows-KommandofensterEclipse unter WindowsLinux
os.name:          Windows 7
default-charset:  windows-1252
file.encoding:    Cp1252
console.encoding: Cp850
Mit System.out:   õ÷³¯ºÇ¢¦??
Mit PrintWriter:  äöüߧ?½²??
Mit PrintStream:  äöüߧ?½²??
os.name:          Windows 7
default-charset:  windows-1252
file.encoding:    Cp1252
console.encoding: Cp1252
Mit System.out:   äöüߧ€½²??
Mit PrintWriter:  äöüߧ€½²??
Mit PrintStream:  äöüߧ€½²??
os.name:          Linux
default-charset:  UTF-8
file.encoding:    UTF-8
console.encoding: UTF-8
Mit System.out:   äöüߧ€½²√∑
Mit PrintWriter:  äöüߧ€½²√∑
Mit PrintStream:  äöüߧ€½²√∑ 

Bemerkung zu "Eclipse unter Windows":
In Eclipse hat das Konsolenfenster nicht die Codepage CP850, sondern die Standard-Codepage (wie "file.encoding", also unter Windows Cp1252). Um unter Windows in Eclipse korrekte Ausgaben zu erzielen, geben Sie unter 'Run' | 'Run Configurations...' | 'Arguments' | 'VM arguments' ein: -Dconsole.encoding=Cp1252.

Bemerkung zum "Windows-Kommandofenster":
Sie können die Codepage des Kommandozeilenfensters (normalerweise 850) mit CHCP umschalten auf eine andere Codepage. Allerdings funktioniert das nur, wenn Sie in den Eigenschaften des Kommandozeilenfensters eine andere Schriftart als die defaultmäßige "Rasterschriftart" wählen, also entweder "Consolas" oder "Lucida Console". Da sich Windows diese Schriftarteneinstellung merkt, empfiehlt es sich, eine Batchdatei zu erstellen, über die Sie das Kommandozeilenfenster mit der anderen Codepage starten, dann merkt sich Windows diese Schriftart für genau die mit dieser Batchdatei gestarteten Kommandozeilenfenster. Die Batchdatei kann beispielsweise für die Windows-1252-Codepage folgenden Inhalt haben:

cmd.exe /k chcp 1252
In dem sich dadurch öffnenden Kommandozeilenfenster stellen Sie einmalig in den Eigenschaften die Schriftart um. Anschließend rufen Sie auf:

javac ConsoleEncoding.java

java -Dconsole.encoding=Cp1252 ConsoleEncoding



Raten des Encodings mit Java

Falls Sie Textdateien mit unbekanntem oder unterschiedlichem Encoding verarbeiten wollen, hilft vielleicht folgende Hilfsmethode EncodingGuess.guessEncoding(). Aber beachten Sie bitte, dass das Encoding selbst bei westeuropäischen Texten nicht immer zuverlässig bestimmt werden kann und dann nur geraten werden kann.

Bei UTF-8-XML-Dateien werden Sie häufig US-ASCII als Ergebnis erhalten (falls keine Umlaute enthalten sind), obwohl die XML-Datei in der ersten Processing-Instruction-Zeile die Definition encoding="UTF-8" enthält. Dies ist kein Widerspruch, da US-ASCII eine Teilmenge von UTF-8 ist.

showMsg sollte normalerweise auf false gesetzt werden, da es nur für Testzwecke interessant ist.


import java.io.*;
import java.nio.charset.Charset;
import java.util.*;

/**
 * Raten des Character-Encodings von Dateien.
 * Achtung: Es wird nicht die gesamte Datei gelesen.
 */
public class EncodingGuess
{
   /** Raten des Character-Encodings von Dateien im angegebenen Verzeichnis. */
   public static void main( String[] args ) throws IOException
   {
      String dir = ( args.length > 0 && args[0].trim().length() > 0 ) ? args[0] : ".";
      showGuessedEncodingsFromFilesInDir( dir );
   }

   /** Raten des Character-Encodings von Dateien im angegebenen Verzeichnis. */
   public static void showGuessedEncodingsFromFilesInDir( String dir ) throws IOException
   {
      String[] files = (new File( dir )).list();
      if( files == null || files.length == 0 ) {
         System.out.println( "Fehler: Keine Datei in '" + dir + "' gefunden." );
         return;
      }
      Arrays.sort( files );
      for( String file : files ) {
         File datei = new File( dir + File.separator + file );
         if( datei.isFile() && !datei.getName().endsWith( ".class" ) ) {
            guessEncodingFromFile( datei, false, true );
         }
      }
   }

   /** Raten des Character-Encodings von der angegebenen Datei. */
   public static String guessEncodingFromFile( File datei, boolean preferISO8859, boolean showMsg ) throws IOException
   {
      String encoding = null;
      try( FileInputStream fis = new FileInputStream( datei ) ) {
         encoding = guessEncoding( fis, preferISO8859, showMsg );
      }
      if( showMsg ) {
         try( BufferedReader isr = new BufferedReader( new InputStreamReader( new FileInputStream( datei ), encoding ) ) ) {
            System.out.println( "Datei " + datei + ", erste Zeile \"" + isr.readLine() + "\": \nEncoding = " + encoding + "\n" );
         }
      }
      return encoding;
   }

   /** Raten des Character-Encodings eines InputStreams. */
   public static String guessEncoding( InputStream is ) throws IOException
   {
      return guessEncoding( is, true, false );
   }

   /** Raten des Character-Encodings eines InputStreams. */
   public static String guessEncoding( InputStream is, boolean preferISO8859, boolean showMsg ) throws IOException
   {
      final String[] boms = { "0000FEFF", "UTF-32BE", "FFFE0000", "UTF-32LE",
                              "EFBBBF", "UTF-8", "FEFF", "UTF-16BE", "FFFE", "UTF-16LE" };
      final String euroUtf16BE = "20AC";
      final String euroUtf16LE = "AC20";
      byte[] ba = new byte[400];
      int len, posArray = ba.length / 2, posStream = 0;
      int anzahlNull = 0, anzahlNonUtf8 = 0, anzahlUtf8 = 0, anzahlUtf16BE = 0, anzahlUtf16LE = 0;
      boolean exists809F = false, existsNonAscii = false;
      boolean ready = false;
      // Es wird in die obere Haelfte des Byte-Arrays ba geladen.
      // In die untere Haelfte wird das vorher geladene Byte-Array kopiert.
      // So koennen auch Zeichen korrekt untersucht werden, die ueber das Ende des Byte-Arrays hinausgehen:
      while( (len = is.read( ba, ba.length / 2, ba.length / 2 )) > 0 && !ready ) {
         if( showMsg && posStream == 0 ) {
            System.out.print( "Erste Zeichen in Hex: " );
            for( int i = ba.length / 2; i < ba.length / 2 + Math.min( len, 30 ); i++ ) {
               System.out.printf( "%02X ", Byte.valueOf( ba[i] ) );
            }
            System.out.println();
         }
         for( int i = ba.length / 2; i < ba.length / 2 + len; i++ ) {
            if( ba[i] == 0 ) { anzahlNull++; }
            if( ba[i] <  0 ) { existsNonAscii = true; }
            int bi = ba[i] & 0xFF;
            if( bi >= 0x80 && bi <= 0x9F ) { exists809F = true; }
         }
         // Es wird vorlaeufig nur bis 4 Bytes vor dem Byte-Array-Ende untersucht:
         int posArrayMax = Math.min( ba.length / 2 + len - 1, ba.length - 4 );
         for( ; posArray < posArrayMax && !ready; posArray++ ) {
            // Auf UTF-16 pruefen:
            boolean u16 = false;
            if( posArray % 2 == 0 ) {
               String hex2 = toHexString( ba, posArray, 2 );
               if( (ba[posArray] == 0 && ba[posArray+1] != 0) || euroUtf16BE.equals( hex2 ) ) {
                  anzahlUtf16BE++;
                  posArray++;
                  u16 = true;
               } else if( (ba[posArray] != 0 && ba[posArray+1] == 0) || euroUtf16LE.equals( hex2 ) ) {
                  anzahlUtf16LE++;
                  posArray++;
                  u16 = true;
               }
            }
            if( !u16 && ba[posArray] <= 0 ) {
               // Nach BOM suchen:
               if( posStream == 0 && posArray == ba.length / 2 ) {
                  String hex = toHexString( ba, posArray, 4 );
                  for( int i = 0; i < boms.length; i+=2 ) {
                     int len2 = Math.min( hex.length(), boms[i].length() );
                     if( boms[i].equals( hex.substring( 0, len2 ) ) ) {
                        return boms[++i];
                     }
                  }
               }
               // Auf UTF-8 pruefen:
               int nu8b = anzahlUtf8Bytes( ba, posArray );
               if( nu8b <= 0 ) {
                  anzahlNonUtf8++;
               } else {
                  anzahlUtf8++;
                  posArray += nu8b - 1;
               }
            }
            ready = anzahlNonUtf8 + anzahlUtf8 + anzahlUtf16BE + anzahlUtf16LE > 50;
         }
         posStream += len;
         posArray  -= ba.length / 2;
         ba = Arrays.copyOf( Arrays.copyOfRange( ba, ba.length / 2, ba.length ), ba.length );
      }
      if( showMsg ) {
         System.out.println( "exists809F=" + exists809F + ", anzahlNonUtf8=" + anzahlNonUtf8 +
               ", anzahlUtf8=" + anzahlUtf8 + ", anzahlUtf16BE=" + anzahlUtf16BE + ", anzahlUtf16LE=" + anzahlUtf16LE );
      }

      if( !existsNonAscii && anzahlNull == 0 ) {
         return ( preferISO8859 ) ? "ISO-8859-1" : "US-ASCII";
      }
      if( anzahlUtf16LE > anzahlUtf16BE && anzahlUtf16LE > anzahlUtf8 && anzahlUtf16LE > anzahlNonUtf8 ) {
         return "UTF-16LE";
      }
      if( anzahlUtf16BE > anzahlUtf8 && anzahlUtf16BE > anzahlNonUtf8 ) {
         return "UTF-16BE";
      }
      if( anzahlUtf8 > 0 && anzahlNonUtf8 == 0 ) {
         return "UTF-8";
      }
      if( exists809F && Charset.availableCharsets().containsKey( "windows-1252" ) ) {
         return "windows-1252";
      }
      return ( existsNonAscii || preferISO8859 ) ? "ISO-8859-1" : "US-ASCII";
   }

   public static int anzahlUtf8Bytes( byte[] ba, int pos )
   {
      if( ba[pos] >= 0 ) { return 1; }
      int i1 = ba[pos+0] & 0xFF;
      if( i1 >= 0x80 && i1 <= 0xC1 ) { return 0; }
      if( i1 >= 0xF5 && i1 <= 0xFF ) { return 0; }
      int i2 = ba[pos+1] & 0xFF;
      if( i2 <  0x80 || i2 >  0xBF ) { return 0; }
      if( i1 >= 0xC2 && i1 <= 0xDF ) { return 2; }
      int i3 = ba[pos+2] & 0xFF;
      if( i3 <  0x80 || i3 >  0xBF ) { return 0; }
      if( i1 >= 0xE0 && i1 <= 0xEF ) { return 3; }
      int i4 = ba[pos+3] & 0xFF;
      if( i4 <  0x80 || i4 >  0xBF ) { return 0; }
      if( i1 >= 0xF0 && i1 <= 0xF4 ) { return 4; }
      return 0;
   }

   public static String toHexString( byte[] ba, int pos, int len )
   {
      StringBuilder hex = new StringBuilder();
      int posEnde = Math.min( pos + len, ba.length );
      for( int i = pos; i < posEnde; i++ ) {
         if( ba[i] >= 0 && ba[i] < 0x10 ) { hex.append( "0" ); }
         hex.append( Integer.toHexString( ba[i] & 0xFF ) );
      }
      return hex.toString().toUpperCase( Locale.GERMAN );
   }
}

Führen Sie aus (Sie können auch einen Verzeichnisnamen als Parameter angeben):

javac EncodingGuess.java

java  EncodingGuess

Um Textdateien in verschiedenen Encodings zu speichern, benötigen Sie einen Editor, der dies unterstützt. Das Windows-7-Notepad bietet zum Beispiel: ANSI (= windows-1252), Unicode (= UTF-16LE), Unicode Big Endian (= UTF-16BE) und UTF-8. UltraEdit 17 bietet noch weitere Encodings im Speicherdialog an. Insbesondere können Sie auch frei wählen, ob die UTF-Formate mit oder ohne BOM gespeichert werden sollen. UltraEdit 17 hat noch einen weiteren entscheidenden Vorteil: Anders als bei vielen anderen Editoren werden im HEX-Modus auch bei UTF-8 die tatsächlich in der Datei gespeicherten Bytes angezeigt.

In Encoding.zip finden Sie ein paar Test-Textdateien mit verschiedenen Encodings und mit und ohne BOM.


Alternativ können Sie die Encoding-Ermittlung mit einem JUnit-Test überprüfen, etwa so:

import java.io.*;
import java.nio.charset.Charset;
import org.junit.*;

/** JUnit-Test fuer EncodingGuess */
public class EncodingGuessTest
{
   private static final String[] ENCODINGS1 = { "US-ASCII", "ISO-8859-1", "windows-1252", "UTF-8", "UTF-16BE", "UTF-16LE" };
   private static final String[] ENCODINGS2 = { "UTF-8", "UTF-16BE", "UTF-16LE" };
   private static final String   MEIN_TEXT1 = "BlaBlupp\r\näöüß\u20AC"; // \u20AC = Euro-Zeichen
   private static final String   MEIN_TEXT2 = "\uFEFF abc";             // \uFEFF = BOM

   @Test
   public void testEncodingGuessFromFile() throws IOException
   {
      // Vor Ausfuehrung Pfade anpassen:
      Assert.assertEquals( "US-ASCII",   EncodingGuess.guessEncodingFromFile( new File( "src/EncodingGuess.java"     ), false, false ) );
      Assert.assertEquals( "ISO-8859-1", EncodingGuess.guessEncodingFromFile( new File( "src/EncodingGuessTest.java" ), false, false ) );
   }

   @Test
   public void testEncodingGuessFromStream() throws IOException
   {
      // Encoding-Erkennung ohne BOM, anhand der verwendeten Zeichen:
      for( String enc : ENCODINGS1 ) {
         if( !Charset.availableCharsets().containsKey( enc ) ) { continue; }
         try( ByteArrayInputStream is1 = new ByteArrayInputStream( MEIN_TEXT1.getBytes( enc ) ) ) {
            Assert.assertEquals( enc, EncodingGuess.guessEncoding( is1, false, false ) );
         }
      }
      // Encoding-Erkennung mit BOM:
      for( String enc : ENCODINGS2 ) {
         try( ByteArrayInputStream is2 = new ByteArrayInputStream( MEIN_TEXT2.getBytes( enc ) ) ) {
            Assert.assertEquals( enc, EncodingGuess.guessEncoding( is2, false, false ) );
         }
      }
      // Bevorzuge ISO-8859-1 statt US-ASCII:
      try( ByteArrayInputStream is3 = new ByteArrayInputStream( MEIN_TEXT1.getBytes( "US-ASCII" ) ) ) {
         Assert.assertEquals( "ISO-8859-1", EncodingGuess.guessEncoding( is3 ) );
      }
   }

   @Test
   public void testHex() throws IOException
   {
      Assert.assertEquals( "426C61426C7570700D0AE4F6FCDF3F", EncodingGuess.toHexString( MEIN_TEXT1.getBytes( "ISO-8859-1" ), 0, 99 ) );
      Assert.assertEquals( "426C61426C7570700D0AC3A4C3B6C3BCC39FE282AC", EncodingGuess.toHexString( MEIN_TEXT1.getBytes( "UTF-8" ), 0, 99 ) );
   }
}

Führen Sie den JUnit-Test entweder in Eclipse aus, oder downloaden Sie junit-4.12.jar und hamcrest-core-1.3.jar, passen Sie die beiden Pfade "src/EncodingGuess...java" in EncodingGuessTest.java an, und führen Sie aus:

javac -cp .;junit-4.12.jar *.java

java  -cp .;junit-4.12.jar;hamcrest-core-1.3.jar org.junit.runner.JUnitCore EncodingGuessTest





Weitere Themen: andere TechDocs | Zeichenkodierung | ASCII-Tabelle
© 2009 Torsten Horn, Aachen