Verwendung von Character Sets in Java

Gerade noch etwas unerfahrene Java Programmierer haben teilweise Probleme mit unterschiedlichen Zeichensätzen. Läuft bei der Entwicklung und dem Test auf der lokalen Maschine noch alles glatt - wieso sich auch Gedanken machen, wenn alles funktioniert - ist die Überraschung teilweise groß, wenn über Schnittstellen fremde Rechner ins Spiel kommen und beispielsweise die Umlaute nicht mehr richtig dargestellt werden.

Generell gilt:

  1. Die interne (im Arbeitsspeicher) Repräsentierung von Strings und Characters erfolgt bei Java Programmen immer im UNICODE Zeichensatz.
  2. Werden Textdateien von der Festplatte oder über das Netzwerk gelesen / geschrieben, nimmt Java an, das diese im Standard-Zeichensatz des Rechners codiert sind, auf dem das Programm ausgeführt wird. Wird eine Textdatei gelesen, übersetzt Java vom Default-Zeichensatz des Rechners nach UNICODE. Wird eine Textdatei geschrieben, übersetzt Java von UNICODE in den Default-Zeichensatz des Rechners.
  3. Sind Textdateien nicht im Standard Zeichensatz des ausführenden Rechners codiert, weil sie beispielsweise auf einem fremden Rechner mit einem anderen Betriebssystem erzeugt wurden, muss Java explizit mitgeteilt werden, um welchen Zeichensatz es sich dabei handelt.

Der von Java verwendete Default-Zeichensatz kann übrigens wie folgt festgestellt werden:

public class DefaultEncoding
{ public static void main(String[] args) throws Exception
  { System.out.println("Default Encoding = " + System.getProperty("file.encoding"));
  }
}

und liefert dann beispielsweise in einer Windows-Umgebung:

C:\temp>java DefaultEncoding
Default Encoding = Cp1252

Links: Java Supported Encodings / Wikipedia Zeichenkodierungen

Aber: woher weiß denn Java eigentlich, ob es sich bei einer Datei um eine Textdatei oder eine Binärdatei (.jpg, .zip, etc.) handelt? Weiß es nicht! Der Entwickler muss wissen was er liest oder schreibt und mit entsprechenden Klassen und Methoden dafür sorgen, dass eine Textdatei entsprechend umcodiert wird und eine Binärdatei belassen bleibt, wie sie ist.

Und hier wartet auch schon der nächste Stolperstein:

  • wird eine Binärdatei mittels Klassen / Methoden gelesen / geschrieben, die dazu gedacht sind, Textdateien zu verarbeiten, ist das Ergebnis mit Sicherheit unbrauchbar,
  • und umgekehrt macht das binäre lesen / schreiben einer Textdatei nur dann Sinn, wenn sie kopiert werden soll und ansonsten unangetastet bleibt.

Verwenden Sie:

  • Reader und Writer Klassen um Textdateien zu lesen und zu schreiben.
  • InputStream und OutputStream Klassen um Binärdateien zu lesen und zu schreiben.
    Aber Achtung: InputStreamReader und OutputStreamWriter zählen zu den Reader und Writer Klassen und sind deshalb nicht zum lesen / schreiben von Binärdateien geeignet!

Wenn Sie unsicher sind, welche Klassen Sie verwenden sollen, sehen Sie nach, ob die Klasse oder deren Elternklasse einen Constructor anbietet, der einen Charset oder einen anderen Reader / Writer als Parameter erwartet. Wenn ja, handelt es sich mit Sicherheit um eine Klasse die nur für das lesen / schreiben von Textdateien geeignet ist.

Weiterhin werden Sie es bei der Verarbeitung von Binärdateien immer mit einer Form von ByteArrays und bei der Verarbeitung von Textdateien immer mit einer Form von Strings, SringBuffern oder CharacterArrays zu tun haben.

Beispiele

Lesen einer Binärdatei:

import java.io.*;
...
byte[] getBinaryFileContent(String fileName) throws IOException
{ BufferedInputStream in = new BufferedInputStream(new FileInputStream(fileName));
  ByteArrayOutputStream bs = new ByteArrayOutputStream();
  BufferedOutputStream out = new BufferedOutputStream(bs);
  byte[] ioBuf = new byte[4096];
  int bytesRead;
  while ((bytesRead = in.read(ioBuf)) != -1) out.write(ioBuf, 0, bytesRead);
  out.close();
  in.close();
  return bs.toByteArray();
}

Schreiben einer Binärdatei:

import java.io.*;
...
void writeBinaryFileContent(String fileName, byte[] content) throws IOException
{ BufferedInputStream in = 
    new BufferedInputStream(new ByteArrayInputStream(content));
  BufferedOutputStream out = 
    new BufferedOutputStream(new FileOutputStream(fileName));
  byte[] ioBuf = new byte[4096];
  int bytesRead;
  while ((bytesRead = in.read(ioBuf)) != -1) out.write(ioBuf, 0, bytesRead);
  out.close();
  in.close();
}

Kopieren einer (Binär- oder Text-) Datei:

import java.io.*;
...
void copyFile(String srcName, String dstName) throws IOException 
{ InputStream in = new FileInputStream(srcName);
  OutputStream out = new FileOutputStream(dstName);
  byte[] ioBuf = new byte[4096];
  int bytesRead;
  while ((bytesRead = in.read(ioBuf)) != -1) out.write(ioBuf, 0, bytesRead);
  out.close();
  in.close();
}

Lesen einer Textdatei mit Default Zeichensatz-Codierung:

import java.io.*;
...
String getTextFileContent(String textFileName) throws IOException
{ StringBuffer content = new StringBuffer();
  String line, lineFeed = System.getProperty("line.separator");
  BufferedReader in = new BufferedReader(new FileReader(textFileName));
  while((line = in.readLine()) != null) content.append(line).append(lineFeed);
  in.close();
  return content.toString();
}

Lesen einer UTF8 codierten Textdatei:
z.B. eine unter Unix erzeugte Textdatei in einem Windows-System verarbeiten

import java.io.*;
...
String getUTF8TextFileContent(String textFileName) throws IOException
{ StringBuffer content = new StringBuffer();
  String line, lineFeed = System.getProperty("line.separator");
  Reader r = new InputStreamReader(new FileInputStream(textFileName), "UTF8");
  BufferedReader in = new BufferedReader(r);
  while((line = in.readLine()) != null) content.append(line).append(lineFeed);
  in.close();
  return content.toString();
}

Schreiben einer UTF8 codierten Textdatei:
z.B. auf einem Windows-System eine Textdatei für Unix erstellen

import java.io.*;
...
void writeUTF8TextFileContent(String textFileName, String content) throws IOException
{ String line, lineFeed = new String("\n"); // Unix LineFeed
  BufferedReader in = new BufferedReader(new StringReader(content));    
  Writer w = new OutputStreamWriter(new FileOutputStream(textFileName), "UTF8");
  BufferedWriter out = new BufferedWriter(w);
  while((line = in.readLine()) != null)
  { out.write(line);
    out.write(lineFeed);
  }
  out.close();
  in.close();
}

Der Java Compiler

Wie bereits oben erwähnt, hält Java alle Buchstaben und Zeichenketten intern im UNICODE Zeichensatz vor. Wird eine Java Quelldatei übersetzt, wird vom Compiler angenommen, dass diese Quelldatei im Default-Zeichensatz des Rechners vorliegt, auf dem der Übersetzungsprozess läuft und alle konstanten Strings werden deshalb vom Default-Zeichensatz nach UNICODE transformiert und so in die .class Datei abgespeichert. Das hat zur Folge, dass eine Java Quelldatei, die zum Beispiel auf einem Unix System verfasst wurde, nicht so ohne weiteres auf einem Windows System übersetzt werden kann, wenn sie beispielsweise in UTF8 codiert ist. Deshalb muss in diesem Fall der Compiler mit einer zusätzlichen Option -encoding UTF8 gestartet werden:

C:\temp>javac -encoding UTF8 MyUTF8CodedProgram.java

Die Java Console

Falls Sie Ihre Java Programme innerhalb einer Windows-Umgebung entwickeln, ist Ihnen sicher schon einmal aufgefallen, dass Umlaute auf der System-Console (System.out) falsch dargestellt werden. Das liegt daran, dass Java davon ausgeht, dass alle Zeichen auf einem Windows-System in der Default-Codierung (z.B.: CP1252) ausgegeben werden müssen. Leider geht die Windows System-Console aber davon aus, dass eingehende Zeichen im alten DOS-Format CP850 codiert sind, weshalb die Ausgaben von Java Programmen nicht richtig dargestellt werden.

Abhilfe schafft ein Trick, bei dem die System.out als Basis für einen Filter herangezogen wird, mit dem die Zeichensatz-Konvertierung vorgenommen wird:

import java.io.*;

public class ConsoleOutput
{ public static void main(String[] args) throws Exception
  { System.out.println("schönes München");
    System.setOut(new PrintStream(
                  new FileOutputStream(FileDescriptor.out),true,"CP850"));
    System.out.println("schönes München");
  }
}
C:\temp>java ConsoleOutput
sch÷nes M³nchen
schönes München