Skip to content

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

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

Christoph on :

[Zitat] 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); [/Zitat] Ich verstehe den Vergleich nicht. Das 1. Beispiel sucht doch nur nach dem 1. char von needle in haystack, während das 2. Beispiel nach dem gesamten String sucht. Was soll denn damit verdeutlicht werden?

eckes on :

Wenn "needle" aus genau einem Zeichen besteht, dann sind die beiden Code-Schnippsel gleich. (Wurde mit Java 1.5 ein erweiteres Zeichen eingegeben, so enthaelt der String dieses eine Zeichen aber 2 char "code units", z.b. "\uD840\uDC00". Diese werden mit einer Substring suche erfolgreich gefunden (wenn sie enthalten sind). Nun gut, ich gebe zu das Beispiel ist etwas an den Haaren herbeigezogen. :) Es trifft aber auch auf equals vergleiche zu. Gruss Bernd

Christoph on :

Dann wäre es doch besser als 2. Beispiel zu nehmen: int pos = haystack.indexOf(needle.substring(0,1)); String res = haystack.substring(pos+1); oder? Gruß, Christoph

eckes on :

Ich bin im Beispiel davon ausgegangen dass der String den richtigen Inhalt hat (nur ein Unicode Zeichen) da er der Returncode eines entsprechenden Controls ist. Das substring klappt nämlich nicht, weil sich die index werte immer auf char units beziehen. D.h. ein substring(0,1) schneidet das supplementary char in der Mitte auseinander. Um wirklich das erste Zeichen aus dem String zu holen kann man keine portable API bemühen. (Jedenfalls wüsste ich keine Methode, ohne zu prüfen ob das Zeichen im Bereich der Surrogate ist). Gruss Bernd

Add Comment

BBCode format allowed
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
To leave a comment you must approve it via e-mail, which will be sent to your address after submission.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA