Java und Unicode
Der native Java Typ char ist vorzeichenloser, ganzzahliger mit einer Breite von 16 bit. Mit dem Wertebereich 0-65535 deckte der Datentyp ursprünglich alle definierten Zeichenwerte des Unicode Standards ab. Dieser Umfang (die sogenannte Basic Multilingual Plane, BMP) umfasst einen Großteil der Zeichen aller aktuell im Gebrauch befindlichen Schriften. Nach Unicode Konvention werden die Zeichenwerte mit U+0000 - U+FFFF (also in Hex Schreibweise mit vorangestelltem 'U+') notiert.
Oberflächlich hat sich damit sicherlich schon jeder Java Entwickler beschäftigt. Ist ja auch eine Grundvoraussetzung zum Verständnis der Unterschiede zwischen einem Zeichen-Reader und einem Byte-Stream.
Wie die meisten Entwickler habe ich mich mit den Details erst befasst, als es Notwendig wurde eine Richtlinie für die Übersetzung unserer J2EE Anwendungen für den asiatischen Markt zu erstellen. Unicode fasst einen Großteil der Schriftzeichen aus Fern-Ost in CJK Unified Ideographs (Unihan) zusammen.
Unicode 4.x definiert aber weitaus mehr Zeichen. Im Moment ist der Bereich bis U+10FFFF für weitere Zeichen vorgesehen. Darunter ein großer Block der zusätzlichen CJK Zeichen. Darin enthalten sind weniger gebräuchliche Schriftzeichen zur Darstellung von Eigennamen und Zeichen die speziell für die Kompatibilität mit nationalen Zeichensätzen reserviert wurden. Erstere werden z.B. für Software in der öffentlichen Verwaltung benötigt. Oder auch nur, wenn es darum geht Dateien von bestehenden Anwendungen verlustfrei zu verarbeiten.
Hier stellt sich jetzt sofort die Frage: "Wie stelle ich mit einem 16bit Java Datentyp die Zeichenwerte mit einer Breite von 20bits dar?" Die Antwort ist erschütternd einfach: gar nicht.
Dieses Problem ist natürlich auch Sun bekannt. Java bietet eine Lösung - oder besser: Workaround - an. Die "Supplementary Characters" die in Java 1.5 eingeführt wurden. Es wurde dazu aber nicht der char Datentyp neu definiert (der bleibt weiterhin bei 16bit), sondern es wurden neue Funktionen und Methoden für den Zugriff auf Zeichenwerte geschaffen, die mit int Werten (signed 32bit) arbeiten.
UTF-16
Zum Glück war ein integraler Bestandteil von Unicode die Erweiterbarkeit in Ebenen.
Neben den echten Zeichenwerten (code points) die aus einem einzelnen teil aus dem Bereich U+0000 - U+D7FF und U+ED00 - U+FFFF bestehen gibt es auch Zeichenwerte die nur als Paar (von code units) auftreten dürfen. Das erste Zeichen aus dem Bereich U+D800 - U+DBFF (der sogenannten High Surrogate Area) wählt die gewünschte Ebene, und das zweite Zeichen aus dem Bereich U+DC00 - U+DFFF (Low Surrogate) wählt das Zeichen in der Ebene aus.
Die Regel lautet nun: Code Points im Bereich U+0000 - U+FFFF werden als den entsprechenden Code Point direkt dargestellt, wobei sichergestellt ist, dass der Bereich U+D800 - U+DFFF nicht verwendet wird. Das hat mehrere Vorteile: die gebräuchlichsten Zeichen können in 16bit dargestellt werden. Als Beispiel: U+20000 wird mit den code units U+D840,U+DC00 dargestellt.
Enthält ein String Zeichen aus dem erweiterten Bereich, so kann man an jeder Position feststellen um was für einen Wert es sich handelt (also kompletter Code Point der BMP oder erstes oder zweiter Teil eines Surrogats). Diese Codierung ist somit selbstsynchronisierend und kompatibel mit Anwendungen die nur die BMP kennen: bei der Schrittweisen Vergleich der einzelnen Stellen eines Strings mit einem bekannten Wert ist sichergestellt dass der bekannte BMP Wert nicht auf eine Erweiterung passt.
Der Nachteil ist, dass die Zeichen keine feste Länge mehr haben, und es damit nicht mehr so einfach wird die Länge eines Strings in Zeichen statt Code Stellen anzugeben. Das ist aber in der Praxis weniger problematisch:
- Für die Speicherung ist die Platzbelegung und damit der Verbrauch an Code Stellen wichtig
- Für die Darstellung ist die pure Anzahl von Code Points sowieso nicht relevant, wegen variabler Zeichenbreite, Überlagerungszeichen und (Steuer)Zeichen ohne Breite.
API Änderungen
Ein Großteil der Methoden der Character Klasse dienen nun zur Umwandlung von int code points in char[] code units, oder zum Zählen der code points in char[] arrays oder CharSequences. Dabei wurde der Wertebereich von Character selbst nicht erweitert, das Object kann nur BMP code points aufnehmen, oder code units der high oder low surrogats repräsentieren. Auch die String Klasse hat einige Erweiterungen im Umgang mit Code Points.
Beispiele
Länge eines Strings s in Code Points:
String s = "äöü\uD840\uDC00";
int l = s.countCodePoint(0,-1); // l contains 4 (not 5) code points
Key/Value Trenner (keine spezielle Behandlung für Surrogats, tut ohne code Änderung in J2SE 1.4 und J2SE 1.5:
String s = "äöü\uD840\uDC00=ÄÖÜ\\";
int pos = s.indexOf('=');
String key = s.substring(0,pos);
String val = s.substring(pos, s.length);
Tipp
In bestimmten Situationen ist es notwendig den Quelltext Ihrer Java Programme zu ändern, um die neuen API Funktionen zu benutzen. Viele Änderungen können aber auch vollkommen transparent und rückwärts kompatibel durchgeführt werden, wenn verstärkt mit Strings statt char-Werten oder Character-Referezen gearbeitet wir.
Hier ein Beispiel: needle ist ein String zurückgeliefert von einem 1-Zeichen Swing GUI Control, haystack ist ein String eingelesen von einer UTF-8 Datei. Mit Java 1.4 würde man den Teilstring von needle ab dem Zeichen mit folgendem Programmfragment suchen:
int pos = haystack.indexOf(needle.charAt(0));
String res = haystack.substring(pos+1);
Code der sowohl mit 1.4 als auch mit 1.5 funktioniert (wobei er bei 1.5 eben auch Supplementary Characters verarbeitet) wäre:
int pos = haystack.indexOf(needle);
String res = haystack.substring(pos+1);
See indexOf(String) in J2SE 1.4 und J2SE 1.5.
Generell kann man sagen, dass Ihr Quelltext in vielen Situationen in denen er mit Strings arbeitet nicht geändert werden muss.
Fazit
Zusammenfassen kann man sagen, dass Java ab 1.5 Unicode 4.0 unterstützt, und daß ein Großteil des Java Codes (siehe 2. Beispiel) funktioniert und auch mit den erweiterten Zeichen umgehen kann. Jedoch ist die Unterstützung ein unschönes Add-On. Bestimmte Quelltext Stellen müssen auch umgestellt werden, damit diese mit den neuen Zeichen arbeiten können. Ebenso dürften die wenigsten Transfer Formate problemlos mit echten UTF-32 Daten zurechtkommen. Von daher ist es fraglich, wie wichtig die Umstellung des Codes auf supplementary characters in der Praxis sein wird. Java 1.5 kennt einige Encodings, die Zeichen aus dem erweiterten Bereich enthalten, darunter z.b. JLS xxxx und GBK. Diese dürften bei Unternehmensanwendungen der Hauptgrund für den Einsatz von erweiterten Zeichen werden.
Zum Weiterlesen
- sun.com Artikel: Supplementary Characters
- JSR-204 - Supplementary Chars
- Bug #4533872 - Supplementary Chars for Tiger (requires Login)
- unicode.org
- W3C/Unicode: Unicode in XML and other MLs
- John O'Conner's Blog: Unicode 4.0 support in J2SE 1.5
- wiki.java.net: Unicode
- Joel on Software: Absolute Minimum Every Software Developer Must Know About Unicode
- java.net: Tom White is counting characters
- UTF-8 Sampler. Many examples collected by the Kermit Project.
- Markus Kuhn's UTF-8 and Unicode FAQ.
Comments
Display comments as Linear | Threaded
Christoph on :
eckes on :
Christoph on :
eckes on :