Der Unicode-Standard definiert lediglich Standard-Codes für Zeichen, nicht konkrete - vom verwendeten Zeichensatz wie Times, Helvetica oder Courier abhängige Darstellungen. Genausowenig schreibt der Unicode-Standard vor, wie die Zeichencodes intern von einem Computer gespeichert oder dargestellt werden müssen.
Unicode-Zeichen sind 16 Bits groß, die natürliche Repräsentation eines Unicode-Zeichens hat demnach auch 16 Bits oder 2 Bytes, und die meisten Systeme definieren heute deshalb einen Datentyp wide character, im Gegensatz zum normalen character (oft auch zum char verkürzt), der mindestens 16 Bit groß ist. Wohlgemerkt, mindestens 16 Bits, nach oben hin sind vom Standard für die Repräsentation keine Grenzen gesetzt, und deshalb sind system- oder codierungsabhängig auch 32-Bit (4 Byte-)Typen gebräuchlich.
Ein Schriftzeichen wird nicht mehr durch ein Byte, sondern durch zwei Bytes dargestellt, und alle Probleme sind gelöst. Oder doch nicht? In der Praxis wirft diese Umstellung erhebliche Probleme auf, neben denen die Euro-Einführung oder die Y2K-Problematik winzig anmuten.
Nehmen wir als Beispiel einen aus dem Zusammenhang gerissenen winzigen Textausschnitt, die Zeichenkette „schreibt über“, die von einem Programm verarbeitet werden soll. Der (Klein-)Buchstabe „s“ hat den ASCII-Code 115, das kleine „c“ hat die Nummer 99, der erste Stolperstein erwartet uns beim kleinen „ü“, von dem wir der Einfachheit halber annehmen, es sei in ISO-8859-1 codiert, habe also die Nummer 252. Insgesamt speichert der Computer die Zeichenkette als Nummernfolge 115, 99, 104, 114, 101, 105, 98, 116, 32, 252, 98, 101, 114. Ist das Programm in C geschrieben, können wir sogar noch davon ausgehen, dass das Ende der Zeichenkette durch ein Pseudo-Zeichen mit der Nummer 0 gekennzeichnet ist.
Die nächsten Überlegungen sind einfacher anzustellen, wenn wir die Nummernfolge nicht mit Dezimalzahlen (also Ziffern von 0-9), sondern mit Hexadezimalzahlen (Ziffern von 0-9 und a-f) darstellen. Es ist nicht notwendig zu verstehen, wie Hexadezimalzahlen interpretiert werden, es reicht völlig aus, zu wissen, dass sie eine andere Darstellung für exakt die gleichen Zahlen sind. In Hex sieht unsere Zeichenfolge so aus:
s c h r e i b t ü b e r
73 63 68 72 65 69 62 74 20 fc 62 65 72
Jede Zweiergruppe von (Hex-)Ziffern entspricht einem Buchstaben (das Leerzeichen Space hat den Code 30, bzw. 20 als Hexzahl).
Jetzt stellen wir unser System auf Unicode-Wide-Characters um, verbraten pro Zeichen also 2 Bytes. Wenn wir annehmen, dass unser Text in einer Datei auf der Festplatte gespeichert ist, wird diese Datei doppelt so groß, denn pro Zeichen werden jetzt doppelt so viele Bits bzw. Bytes verwendet. Leider weiß unser Programm nichts von dieser Umstellung (es muss noch umprogrammiert werden), und wird beim nächsten Leseversuch folgendes sehen:
s c h r e i b t ü b e r
00 73 00 63 00 68 00 72 00 65 00 69 00 62 00 74 00 20 00 fc 00 62 00 65 00 72
Die Zeichen wurden doppelt so groß, aus Sicht unseres noch an Bytes gewöhnten Programms wurden sie also mit Nullen aufgefüllt, obwohl noch immer der gleiche Text gemeint ist. Computerprogramme sind dumm, unser Programm macht da keine Ausnahme und wird diese Änderung einfach hinnehmen. Aber was sehen wir, wenn das Programm den Text ausgeben soll? Das hängt von Programminterna ab. Eine Möglichkeit lautet: Nichts! Weshalb? Ein Null-Byte ist in der am weitesten verbreiteten Programmiersprache, der Spache C mit der speziellen Bedeutung End Of String, also Ende der Zeichenkette belegt. Unglücklicherweise fängt unsere Unicode-Zeichenkette fast zwangsläufig schon mit genau diesem Zeichen an, und konsequenterweise gibt unser Programm jetzt eine leere Zeichenkette aus. Dieser Fall (wir sehen nichts) dürfte der häufigste sein, wenn zwei nicht-unicodefähige Programme wide characters austauschen.
Weitaus weniger wahrscheinlich ist, dass das Programm einfach über die nicht darstellbaren Null-Bytes hinweggeht, und alles andere ausgibt. Damit hätten wir erstmal keine Probleme, aber welche Länge wird unser Programm wohl für die Zeichenkette annehmen? 13 oder 26 Zeichen? Und wer weiß, an welchen anderen Stellen die Null-Bytes doch noch für Verwirrung sorgen, und Schaden anrichten?
Der häufigste Fall, wenn die Daten von einem Datenträger gelesen werden (dann gilt die Konvention mit dem Null-Byte nämlich nicht), dürfte der sein, dass das Programm die nicht-darstellbaren Null-Bytes Ersatzzeichen einfügt, wir sehen etwas wie „?s?c?h?r?e?i?b?t? ?ü?b?e?r“.
Wem das noch nicht erschreckend genug erscheint, möge sich vorstellen, dass wir Unicode jetzt richtig ausreizen wollen, und unseren Text endlich etwas weiterschreiben können: „schreibt über Anna Karenina“, wobei wir allerdings den Titel Tolstois Roman im russischen Original darstellen wollen:

Schön wär's. Wahrscheinlicher wird uns unser Programm aber Zeichenmüll in der Art von „?s?c?h?r?e?i?b?t? ?ü?b?e?r^D?^D=^D=^D0^D ^D?^D0^D@^D5^D=^D8^D=^D0“ präsentieren. Der deutsche Part ist noch einigermaßen zu entziffern, der kyrillische Part ist ein Zeichensalat aus willkürlich anmutenden Schriftzeichen, die sich mit Steuerzeichen (meistens das ASCII-Zeichen mit der Nummer 4 End of Transmission - EOT, der Einfachheit halber durch die Zeichenfolge „^D“ ersetzt) abwechseln.
Alles nicht wünschenswert. Also werden wir zusehen, dass wir unsere Programme möglichst schnell auf Unicode umstellen, dass sie also intern alle Textdaten als wide characters erwarten. Versäumen wir es jedoch, den Rest der Welt von der Löblichkeit unseres Vorhabens zu überzeugen, wird eines Tages dennoch eine alte Datei mit 8-Bit-Zeichen den Weg in unser kleines Refugium finden. Bleiben wir bei unserem Beispiel, wir lesen die ursprüngliche, noch nicht nach Unicode konvertierte Datei:
s c h r e i b t ü b e r
73 63 68 72 65 69 62 74 20 fc 62 65 72
So war es jedenfalls einmal gemeint. Tatsächlich erwartet unser Programm aber, dass jedes Zeichen in zwei Bytes kodiert ist. Der erste Stein rollt uns jetzt in den Weg, weil wir eine ungerade Anzahl von Bytes einlesen, was nie passieren könnte, wenn wir zwei Bytes pro Zeichen verwenden. Wenn wir Pech haben (das hängt vom Talent des Programmiers ab) fliegt uns unser Programm mit Karacho um die Ohren, wenn wir Glück haben, ignoriert das Programm das überflüssige Byte am Ende oder wird wieder stillschweigend ein Ersatzzeichen darstellen, und die (einzelnen) Bytes werden jeweils zu wide characters (doppelten Bytes) zusammengefasst. Das Programm liest:
s c h r e i b t ü b e
7363 6872 6569 6274 20fc 6265
Hex-Zahlen haben die angenehme Eigenschaft, dass für ein Byte nie mehr als zwei „Ziffern“ gebraucht werden, und man die Bytes einfach nur zusammenschieben muss, um zu Multi-Byte-Darstellungen zu gelangen. Treudoof als Unicode interpretiert sähe unser (deutscher!) Text jetzt aber so aus:

Nein, das ist nicht unser deutscher Text in japanischer Lautschrift. Noch weniger handelt es sich um eine computergestützte Übersetzung von deutsch auf japanisch. Dass hier ostasiatische Zeichen dargestellt werden, ist reiner Zufall, weil sie gerade im Bereich der falsch interpretierten Daten liegen. Tatsächlich steht hier Nonsens (hoffe ich doch jedenfalls).
All dies könnte man jedoch als Übergangsschwierigkeiten betrachten. Nach einer gewissen Zeit könnten sämtliche Softwareprogramme an die neue 16-Bit-Konvention angepasst werden, und wir wären da, wo wir hinwollen. Selbst dann wäre diese Konvention aber noch mit erheblichen Nachteilen belastet.
Auch jenseits allen westeuropäisch-amerikanischem Chauvinismus', ist die Tatsache nicht zu verleugnen, dass ein ganz großer Teil der (Text-)Dateien, die von Computern verarbeitet und zwischen ihnen ausgetauscht werden, mit dem 8-Bit-Codeset ISO-8859-1 (bzw. Windows-1252) kodiert ist, und diese Codierung auch für eine adäquate Darstellung geeignet ist. Viele weitere Sprachen kommen ebenfalls prinzipiell mit einem 8-Bit-Zeichensatz aus. In einer reinen Unicode-Welt, würden diese Daten plötzlich alle auf die doppelte Größe aufgebläht, würden doppelt so viel Platz auf Festplatten und im Speicher verbrauchen, würden bei der Übertragung im Netzwerk die doppelte Bandbreite verschlingen und würden auch (mindestens!) die doppelte Zeit für ihre Verarbeitung benötigen. Neben den Kompatibilitätsschwierigkeiten ist dieser Aspekt sicher der wichtigste Grund, der einer Migration von 8 nach 16 Bit für Textdaten entgegensteht.
Theoretisch ließen Computer sich vollständig auf die neue 16-Bit-Konvention umstellen. Praktisch wird dies unmöglich sein. Bei Netzwerkprotokollen interessiert es beispielsweise nicht wirklich, ob es sich bei den übertragenen Daten um Text oder Bilder, Sound, Video, Programmdateien oder was auch immer handelt. Die Daten werden weiterhin als Byteströme interpretiert werden. Das gleiche gilt, wenn Daten aus Gerätedateien (Festplatten, Sound-/Grafikkarten, etc.) gelesen werden. Auch hier wird sich in absehbarer Zeit nichts ändern, und diese Vorgänge werden weiterhin byteorientiert stattfinden.
Bei Datenströmen kommt es jedoch nicht selten vor, dass der Lesevorgang irgendwo, mitten im Datenstrom beginnt oder endet. Entsprechen die Bytegrenzen gleichzeitig Zeichengrenzen ist dies kein Problem. Bei wide characters, also 2-Byte-Zeichen wird das Lesen solcher Datenströme zur Herausforderung, denn die lesende Applikation kann immer nur Paare von Bytes sinnvoll interpretieren. Kann nicht ermittelt werden, ob das erste Byte eine gerade oder ungerade Hausnummer hat, können die Daten auf zwei unterschiedliche Arten interpretiert werden, mit anderen Worten: Es kann nicht ermittelt werden, ob das erste Byte im Datenstrom das vordere oder hintere Byte eines wide characters ist.

Diesem CJK-Silbenzeichen wurde im Unicode-Standard die Nummer 17.210 (hexadezimal 0x433a) zugewiesen. Darüber werden wir uns nicht wenig freuen, bis wir auf die Idee kommen, einer Datei auf einem Windows-Rechner einen Namen zu geben, der mit diesem Zeichen beginnt. Zerlegen wir die Zahl 17.210 nämlich in zwei einzelne Bytes, erhalten wir 67 und 58, und das sind die ASCII-Codes für die Zeichen „C“ und „:“. Es bleibt eine Übungsaufgabe für die Leserin, die Unicode-Zeichenfolge zu bestimme, die (fälschlicherweise) als ASCII interpretiert C:\WINNT\system\very_important.dll ergibt.
Dies ist natürlich kein Windows-Problem. Alle Dateisysteme haben „verbotene“ Zeichen in Datei- und Verzeichnisnamen. Solange die Dateisysteme aber nicht unicodefähig sind, kann es zu bösen Überraschungen kommen, wenn eine Unicode-Byte-Folge zufällig solche verbotenen Zeichen enthält.
Lemuel Gulliver, zunächst Schiffsarzt dann Kapitän auf einigen Schiffen, wurde folgendes auf seiner Reise in das Land Lilliput kolportiert (Quelle [Gulliver's Travels]):
It began upon the following Occasion. It is allowed on all Hands, that the primitive way of breaking Eggs, before we eat them, was upon the larger End: But his present Majesty's Grand-father, while he was a Boy, going to eat an Egg, and breaking it according to the ancient Practice, happened to cut one of his Fingers. Whereupon the Emperor his Father published an Edict, commanding all his Subjects, upon great Penaltys, to break the smaller End of their Eggs. The People so highly resented this Law, that our Histories tell us there have been six Rebellions raised on that account; wherein one Emperor lost his Life, and another his Crown. These civil Commotions were constantly fomented by the Monarchs of Blefuscu; and when they were quelled, the Exiles always fled for Refuge to that Empire. It is computed, that eleven thousand Persons have, at several times, suffered Death, rather than submit to break their Eggs at the smaller End. Many hundred large Volumes have been published upon this Controversy: But the books of the Big-Endians have been long forbidden, and the whole Party rendered incapable by Law of holding Employments. During the Course of these Troubles, the Emperors of Blefuscu did frequently expostulate by their Ambassadors, accusing us of making a Schism in Religion, by offending against a fundamental Doctrine of our great Prophet Lustrog, in the fifty-fourth Chapter of the Brundrecal (which is their Alcoran.) This, however, is thought to be a meer Strain upon the Text: For the Words are these: That all true Believers shall break their Eggs at the convenient End: and which is the convenient End, seems, in my humble Opinion, to be left to every Man's Conscience, or at least in the power of the Chief Magistrate to determine. Now the Big-Endian Exiles have found so much Credit in the Emperor of Blefuscu's Court, and so much private Assistance and Encouragement from their Party here at home, that a bloody War has been carried on between the two Empires for six and thirty Moons with various Success; during which time we have lost forty Capital Ships, and a much greater number of smaller Vessels, together with thirty thousand of our best Seamen and Soldiers; and the Damage received by the Enemy is reckon'd to be somewhat greater than Ours.
Gut zwei Jahrhunderte später brach ein ähnlich heftiger Glaubenskrieg um die Frage aus, wie Zahlen größer als 255 (also Zahlen, zu deren Darstellung man mehr als ein Byte braucht) intern darzustellen seien. Aus Abschnitt 2.2, „Von Bits und Bytes“ erinnern wir uns, dass der Trick prinzipiell darin besteht, diese großen Zahlen in Kombinationen von zwei, vier oder sogar acht Bytes darzustellen. So wie es nur die Ziffern 0-9 gibt, können wir dennoch Zahlen beliebiger Größe mit diesen Ziffern darstellen, wenn wir nur einfach genügend Ziffern aneinanderreihen. Die Zahl zwölf wird als Kombination aus eins und zwei dargestellt, wobei wir die erste Ziffer einfach mit zehn multiplizieren (12 = 1 x 10 + 2). Bei den Bits und Bytes gilt das gleiche, nur dass wir hier jeweils mit 256 multiplizieren müssen. Wenn wir die beiden Bytes 67 (binär 01000011) und 58 (binär 00111010) aneinanderreihen, erhalten wir also die Zahl 17.210 = 67 x 256 + 58. Oder - um bei den uns vertrauten Glühbirnen zu bleiben:

Das scheint doch sehr klar. Oder doch nicht? Wie wäre es, wenn wir die beiden Bytes nicht von links nach rechts, sondern von rechts nach links anordnen?

Das erscheint zunächst ziemlich abwegig, aber es gab Hardwarehersteller, die genau diesen Weg gingen, bei Byte-Folgen also das weniger signifikante Byte (Least Significant Byte - LSB) vor das Most Significant Byte (MSB) setzten. Nein, diese Hardwarehersteller kamen nicht aus der arabischen Welt, sondern aus dem Silicon Valley in Kalifornien. Und, nein, LSB-Prozessoren sind keine exotische Ausnahme; die Intel- (oder Intel-kompatiblen) Chips, die sich in gängigen PCs finden, gehören allesamt zur LSB-Fraktion, würden die Zahl zwölf - um bei der Analogie zu bleiben - also als „21“ darstellen.
Die prominentesten Vertreter des anderen, des MSB-Lagers, sind die Prozessoren der Firmen Sun Microsystems und Motorola, sowie die meisten Prozessoren der Risc-Architecktur, und weil die Standpunkte der Verfechter der einen und der anderen Fraktion ähnlich unvereinbar, und ihre Auseinandersetzungen ähnlich unversöhnlich wie die der Big- und Little-Endians in Lilliput waren, bürgerte es sich ein, die beiden Byte-Sex- (sic! Auch dies ist ein Terminus Technicus!) Varianten als big-endian (MSB) und little-endian (LSB) zu bezeichnen. In der Praxis muss also bei jeder Zahl über 255 dazugesagt werden, welcher Konvention folgend sie denn abgespeichert ist, was einigen Rechenaufwand verursachen kann. In praktisch allen Netzwerk-Protokollen wird eine Big-Endian-Darstellung postuliert, was dazu führt, dass Little-Endian-Intel-Boxen alle Zahlen intern an den Schnittstellen zum Netzwerk drehen müssen, selbst, wenn sie unter sich bleiben.
Einigermaßen unangenehm ist es, wenn der Byte-Sex (weniger zweideutig auch als Endianness bezeichnet) von Daten nicht bekannt ist, weil dies natürlich, zu einer Ambivalenz bei der Interpretation der Daten führt. Speichern wir einen Text mit Wide Characters auf einem Intel-PC ab, und übertragen ihn auf einen Sparc-Server der Firma Sun, haben wir ein handfestes Problem, weil die beiden Rechner unterschiedliche Vorstellungen über die Interpretation der Daten haben.
Allen Schwierigkeiten zum Trotz hat die Darstellung von Unicode-Zeichen als 2-Byte-Folgen eine relativ große Verbreitung gefunden, nicht zuletzt, weil dies die native Darstellung textueller Daten in der Programmiersprache Java ist. Folge dieser Designentscheidung in Java ist ein erhöhter Speicherverbrauch, andere negative Effekte sind weniger relevant, solange es sich um ein geschlossenes System handelt, bei dem die Interpretation von Daten (Bytes) eindeutig festgelegt ist.
Tatsächlich gibt es aber zwei (weitere) Varianten von UTF-16 (16-Bit Unicode Transformation Format), nämlich eine Big-Endian- und eine Little-Endian-Variante. In Abwesenheit anderer Anhaltspunkte sollte von Big-Endianness ausgegangen werden. Ein konkreter Anhaltspunkt ist das Unicode-Zeichen mit der Nummer 65.279 (hexadezimal feff). Dieses Zeichen ist „illegal“, es ist das sogenannte Byte Order Mark - BOM.
Der Nutzen des BOM erschließt sich erst dann, wenn man sich überlegt, wie das Zeichen bei „falschem“ Byte-Sex interpretiert wird. Liest ein Big-Endian-System ein BOM aus Daten, die auf einem Little-Endian-System erzeugt wurde, sieht es nämlich nicht die Zahl 65.279 (Hex feff), sondern - da die beiden Bytes vertauscht sind - die Zahl 65.534 (Hex fffe), und das Unicode-Zeichen mit der Nummer 65.534 ist im Unicode-Standard ebenfalls als illegal gekennzeichnet. Konsequenz: Bei allen folgenden Byte-Paaren müssen die Bytes jeweils vertauscht werden, und das gilt praktischerweise sowohl für Big-Endian- als auch für Little-Endian-Systeme, was wiederum die Portabilität von Programmen auf Quelltextebene erleichtert.
Es ist nicht verwunderlich, dass das BOM normalerweise das erste Zeichen in einer Textdatei ist. Clevere Programme interpretieren es aber an beliebigen Stellen (Rationale: Die Datei könnte vom User aus Einzeldateien zusammengesetzt worden sein).
Bei den sogenannten Surrogaten handelt es sich um eine Kombination mehrerer Unicode-Zeichen, die speziell interpretiert werden, um die Beschränkung auf theoretisch maximal 65.536 Zeichen zu sprengen. In der Praxis ist dies wenig relevant, weil die Surrogate in den meisten Fonts - die ja zur endgültigen Darstellung der Textdaten notwendig sind - schlichtweg fehlen.
Die Unterschiede zwischen UTF-16 und UCS-2 sind mehr akademischer Natur. Der Name ist aus Universal Character Set und 2 Bytes zusammengesetzt.
Mit 2 Bytes/8 Bits lassen sich theoretisch nur 65.536 Zeichen repräsentieren; Unicode sieht über den Extension-Mechanismus aber mehr als eine Million weiterer Zeichen vor. Diesen Zeichen wurde natürlich genauso eindeutige Nummern zugewiesen, und konsequenterweise umgehen viele Systeme den zusätzlichen Aufwand, den der Extension-Mechanismus verursacht, indem sie gleich 4 Bytes (32 Bits) für Wide Characters reservieren. Tatsächlich ist dies nicht die Ausnahme, sondern die Regel. Das Codeset UCS-4 (und die Varianten UCS-4BE und UCS-4LE) ist dort die „natürliche“ interne Repräsentation von Unicode-Daten, und wer als Programmiererin auf den Datentyp Wide Character (in C wchar_t) stößt, sollte eher mit 4-Byte- bzw. 32-Bit-Daten rechnen.
Da die „natürliche“ Repräsentation von Unicode-Daten durch Wide Characters unter etlichen Nachteilen leidet, werden solche Daten oft mit variablen Zeichenfolgen, Multi-Byte-Encodings, kodiert. In diesen Kodierungen hat ein Zeichen keine fixe Länge in Bytes; vielmehr werden Zeichen durch einen Escaping-Mechanismus in eine Byte-Folge variabler Länge konvertiert.
UTF-7 steht für Universal Transformation
Format 7 Bits, und erfreut sich mäßiger
Beliebtheit, jedoch einiger Verbreitung insbesondere im
E-Mail-Verkehr. Wir wissen ja bereits, dass E-Mail strenggenommen
nur ASCII-Zeichen, also 7-Bit-Zeichen akzeptiert, sprich
noch nicht einmal Umlaute, akzentuierte oder andere im Standard
für 8-Bit-Zeichensätze ISO-8859-1 definierte Zeichen
klarkommt. UTF-7 umgeht diese Schwierigkeiten, indem beliebige
(Unicode-)Zeichen mithilfe von ASCII-Zeichen repräsentiert
werden. Erreicht wird dies durch eine Sonderinterpretation des
Pluszeichens. Ein Pluszeichen leitet immer eine Escape-Sequenz
ein, die ein oder mehrere Nicht-ASCII-Zeichen repräsentiert,
ein Minuszeichen beendet sie. Die Zeichenkette „Große
Füße“ stellte sich in ASCII beispielsweise als
Gro+AN8-e F+APwA3w-e dar. Dies
lässt sich mit einiger Fantasie noch entschlüsseln. Fragen wir
hingegen auf Russisch
, stellt sich dies schon als
+BCcEQgQ+
+BDQENQQ7BDAEQgRM? dar. Viel mehr als das
Leer- und Fragezeichen dürfte hier nicht entzifferbar sein.
UTF-7 wurde in [RFC2152] standardisiert.
Der schlechte Ruf dieser Codierung rührt nicht zuletzt daher, dass einige minderwertige Mail-Programme für MS-DOS-Derivate dieses Codeset oft ganz und gar überflüssigerweise verwenden, obwohl wesentlich verbreitertere und elegantere Alternativen existieren). Ein ganz entscheidender Nachteil ist weiterhin die Tatsache, dass eine UTF-7-Codierung nicht eineindeutig ist, das heißt, ein und die selbe (Unicode-)Zeichenfolge lässt sich auf verschiedene Arten in UTF-7 kodieren. Es wurde bereits dargelegt, weshalb dies beispielsweise eine Volltextsuche über einen Text erheblich verkompliziert.
UTF-8 ist der De-Fakto-Standard für den Austausch von Unicode-Daten. Diese Kodierung ist auch unter den Namen FSS_UTF (file-system-safe Universal Transformation Format), oder UTF-2 (Version 2 des Universal Transformation Format) bekannt. Die Kodierung ist in [RFC2279] standardisiert.
Der Alias UTF-FSS deutet bereits an, dass diese Kodierung etliche Nachteile der anderen Methoden umgeht, dies sogar auf eine außerordentliche clevere Art und Weise. Wie funktioniert UTF-8 also?
In UTF-8 wird jedes ASCII-Zeichen durch sich selbst dargestellt, Sonderzeichen durch eine Folge von Nicht-ASCII-Zeichen. Der Aufbau dieser Multi-Byte-Folgen ist wiederum äußerst geschickt definiert. Eine solche Folge beginnt immer mit einem Byte, dessen höchstes und zweithöchstes Bit immer gesetzt, also Eins sind. Die Anzahl der nachfolgenden gesetzten Bits bestimmt die Länge der Multi-Byte-Folge:
Tabelle 2.1. Anfangsbyte von UTF-8
| Erstes Byte | Länge des Zeichens in Bytes |
|---|---|
| 0xxxxxxx | 1 |
| 110xxxxx | 2 |
| 1110xxxx | 3 |
| 11110xxx | 4 |
| 111110xx | 5 |
| 1111110x | 6 |
Eine Anwendung muss also lediglich die Anzahl der gesetzten Anfangs-Bits eines Bytes „zählen“, um die Länge des jeweiligen Zeichens zu ermitteln. Bei ASCII-Zeichen ist das erste (von acht) Bits nicht gesetzt (ASCII ist eine 7-Bit-Codierung), das Zeichen hat eine Länge von eins, es repräsentiert sich - im ASCII-Sinne - selbst. Sind mindestens zwei Anfangsbits gesetzt, lässt sich durch Zählen dieser Bits die Länge des Zeichens ermitteln.
Die nachfolgenden Bytes einer Multi-Byte-Sequenz entsprechen stets dem Muster 10xxxxxx, also einem gesetzten, gefolgt von einem nichtgesetzten Bit. Die bisher nicht besprochenen Bits (durch „x“ dargestellt) werden zur Codierung des eigentlich gemeinten Unicode-Zeichens verwendet. Bei 2-Byte-Sequenzen, die dem Muster 110xxxxx 10xxxxxx entsprechen müssen, zählen wir insgesamt elf mal „x“, haben also elf Bits zur Codierung des Zeichens zur Verfügung. Mit diesen elf Bits lassen sich schon 2047 (hexadezimal 7ff) Zeichen darstellen. In der Übersicht:
Tabelle 2.2. Prinzip von UTF-8
| Muster | Bits | Wertebereich | |
|---|---|---|---|
| Hexadezimal | Dezimal | ||
| 0xxxxxxx | 7 | 0-7f | 0-127 |
| 110xxxxx 10xxxxxx | 11 | 80-7ff | 128-2.047 |
| 1110xxxx 10xxxxxx 10xxxxxx | 16 | 800-ffff | 2.048-65.535 |
| 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 21 | 10000-1fffff | 65.535-131.071 |
| 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx | 26 | 200000-3ffffff | 131.072-67.108.863 |
| 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx | 31 | 4000000-7fffffff | 67.108.864-2.147.483.647 |
Wie werden Zeichen konkret in UTF-8 kodiert? Nehmen wir als
Beispiel die bereits erwähnte arabische Ligatur für Gott
bzw. Allah
, der in Unicode die Nummer 65.010
(Hexadezimal fdf2) zugewiesen wurde. Wir werden jetzt
schrittweise die Kodierung dieses Zeichens in UTF-8
entwickeln. 65.010 liegt im Bereich 2.048-65.535, wir können
obiger Tabelle also entnehmen, dass das Zeichen in insgesamt drei
Bytes kodiert wird, die folgendem Muster entsprechen:
1110xxxx 10xxxxxx 10xxxxxx
Das erste Byte beginnt mit drei gesetzt Bits, die gesamte Multi-Byte-Folge hat also die Länge 3 (in Bytes). Es bleiben noch 16 Bits für die Kodierung des Zeichens übrig. Als nächstes wandeln wir die Dezimalzahl 65.010 in eine Binärdarstellung um:
65.010 (dezimal)
fd f2 (hexadezimal)
f d f 2 (hexadezimal)
1111 1101 1111 0010 (binär)
Diese 16 Bits müssen jetzt in die Schablone für die Zeichen 2.048-65.535 „eingesetzt werden“.
11111101 11110010
1110xxxx 10xxxxxx 10xxxxxx
Neugruppiert:
1111 110111 110010
1110xxxx 10xxxxxx 10xxxxxx
Eingesetzt:
11101111 10110111 10110010
e f b 7 b 2
Das Zeichen wird also in die Drei-Byte-Folge ef b7 b2 transformiert. Diese drei Bytes würden im geläufigeren ISO-8859-1 als „ï·²“ dargestellt, also als kleines i mit zwei Punkten, als Multiplikationspunkt, gefolgt von einer hochgestellten zwei. Dass unsere Konversion korrekt ist, lässt sich mit einem einfachen Experiment feststellen: Wenn wir die drei Zeichen ï·² per Cut & Paste in eine Text-Datei in kopieren, und im Browser darstellen, wird der Browser sie zunächst als ISO-8859-1 interpretieren. Die meisten Browser bieten im Menü die Möglichkeit, das Codeset für ein Dokument manuell zu setzen. Wählen wir dort müsste unsere arabische Ligatur erscheinen, vorausgesetzt, der eingesetzte Zeichensatz enthält dieses Zeichen.
Versuchen wir das Zeichen mit der Maus zu selektieren, stellen wir fest, dass der Browser es wirklich als ein einziges Zeichen interpretiert, es lässt sich nur komplett selektieren.
Falls das nicht klappt, liegt es vermutlich daran, dass der Browser beziehungsweise das Betriebssystem nicht in der Lage sind, arabische Texte darzustellen. In diesem Falle wiederholen wir das Experiment mit der Zeichenkette „übermütig“. Das kleine ü wird in UTF-8 durch die zwei Bytes c3 bc (hexadezimal) repräsentiert. Diese beiden Bytes entsprechen einem großen A mit Tilde und dem Zeichen für ein Viertel, also „ü“, insgesamt wird übermütig in UTF-8 also zu übermütig. Pasten wir diese Zeichenkette in eine Text-Datei, und verstellen das Browser-Codeset auf UTF-8, können wir den Effekt ebenfalls beobachten.
Welche Eigenschaften von UTF-8 können wir ausmachen?
ASCII-Zeichen bleiben unangetastet. Sie stehen für sich selbst, das heißt, das UTF-8 file-system-safe ist, und z. B. auch Programmcode (der in aller Regel zwingendermaßen in US-ASCII geschrieben ist) voll gültig bleibt. Dies gilt für alle Anwendungen, in denen traditionell die Verwendung von Sonderzeichen vermieden wird, wie Dateinamen (File-System-Safety), E-Mail-Headern, Anweisungen in Imperia-Templates, ...
Besonders wichtig ist auch, dass ein Null-Byte in UTF-8-Daten für sich selber steht, es kann also niemals Teil eines gültigen UTF-8-Zeichens sein, und ist weiterhin geeignet zur Kennzeichnung des Endes einer Zeichenkette.
Texte, die in Obermengen von US-ASCII verfasst sind, wie z. B. westeuropäische Texte bleiben zumindest noch deutbar. Die Zeichenkette „übermutig“ stellt sich - fälschlicherweise als ISO-8859-1 interpretiert - als übermütig dar. In Texten mit weniger Sonderzeichen und mehr Kontext-Informationen ist der Negativ-Effekt deutlich geringer ausgeprägt.
Die ASCII-Transparenz von UTF-8 ist für den praktischen Gebrauch enorm wichtig. Etliche Anwendungen (Beispiele hierfür sind der Linux-Kernel, C, oder auch Imperia) werden dadurch unicodefähig, dass sie Unicode größtenteils schlichtweg ignorieren, also einfach nichts in die zu verarbeitenden Daten hineininterpretieren, sie weiterhin als Byteströme betrachten, weil sie wissen, dass die für die Anwendung „interessanten“ Bytes, die traditionell im ASCII-Bereich liegen, weiterhin eindeutig zu erkennen sind. Anwendungen bzw. Systeme, die den gegenteiligen Ansatz verfolgen (Beispiele sind Java, XML oder auch Perl), also die interne Interpretation eines „Zeichens“ ändern, erkaufen sich diesen Ansatz mit erheblichen Performanceeinbußen und umständlichen Schnittstellen, ohne Gewinne bei der Funktionalität.
Auch das Problem der Synchronisationsfähigkeit in Byte-Strömen ist auf elegante Weise gelöst. Stößt eine Anwendung in einem UTF-8-Strom auf ein Byte, dessen erstes Bit nicht gesetzt ist, kann es sofort aufsetzen, weil es sich um ein normales ASCII-Zeichen handeln muss. Sind mindestens die ersten beiden Bits gesetzt, weiß die Anwendung, dass sie den Anfang einer Multi-Byte-Folge erwischt hat, und ist ebenfalls sofort synchronisiert. Ist dagegen das erste Bit gesetzt und das zweite nicht, sind wir auf den casus irreducibilis gestoßen, auf eine unvollständige Multi-Byte-Sequenz, und die Anwendung muss die folgenden Bytes ignorieren, bis sie auf ein Byte stößt, dass entweder für sich selber steht (7-Bit) oder den Anfang einer neuen Multi-Byte-Sequenz kennzeichnet. Das ist nicht wirklich tragisch, die Anwendung hat ja tatsächlich mitten in einem Buchstaben angefangen zu lesen, und in dieser Situation ist es sicher legitim, das unvollständig gelesene Zeichen zu ignorieren.
Auch die Bilanz beim Speicherverbrauch ist durchweg positiv. Bei ASCII-Texten/-Zeichen gibt es keinen Unterschied. Bei Texten, die traditionell in 8-Bit-Zeichensätzen dargestellt wurden (das ist in etwa der Bereich von 128-2.047) werden statt bisher maximal 8 Bit jetzt 16 Bit pro Zeichen verbraucht. Dies relativiert sich allerdings, wenn - wie in den meisten europäischen Sprachen - der ASCII-Anteil überwiegt (deutsche Texte werden typischerweise um weniger als 5 Prozent „aufgebläht“). In nicht-lateinischen Alphabeten (z. B. kyrillisch) liegt die Rate knapp unter 100 % (unter der Annahme, dass die Interpunktions- und Steuerzeichen identisch sind), bei anderen Sprachen, die mit der Basic Multilingual Plane - BMP (Unicode 0-65534) darstellbar sind, hängt der Faktor vom Referenz-Codeset ab, dürfte in der Regel jedoch knapp unter 50 % liegen (2 Bytes vs. 3 Bytes). Die Gesamtbilanz ist durchaus annehmbar.
Die Nachteile von UTF-8 sollen jedoch auch nicht verschwiegen werden. Beim Speicherverbrauch begünstigt UTF-8 Sprachen mit lateinischen Alphabeten und „diskriminiert“ andere, auch wenn die tatsächlichen Faktoren einen ziemlich vorteilhaften Kompromiss darstellen.
Außerdem ist die Dekodierung von UTF-8 mit einigem Rechenaufwand verbunden, die oben beispielhaft durchgeführte Kodierung der arabischen Ligatur für Allah sieht nicht nur kompliziert aus, sie ist es auch. Noch komplizierter ist es jedoch aus der UTF-8-Darstellung zurückzugelangen, also UTF-8-Ströme zu dekodieren. Auch triviale Aufgaben wie die Bestimmung der Länge einer Zeichenkette (in byteorientierten Anwendungen läuft das auf stupides Zählen der Bytes hinaus) werden plötzlich zur Herausforderung.
Zusätzlich ergibt sich die Schwierigkeit, dass UTF-8 „Lücken“ aufweist, illegale Multi-Byte-Sequenzen, die in kein gültiges Unicode-Zeichen umgewandelt werden können. Bei der Dekodierung müssen ständig Vorkehrungen für diesen Fall getroffen werden, was eine Anwendung erheblich verlangsamen kann.
Schließlich ist auch die relative Ähnlichkeit von UTF-8 zu ASCII gleichzeitig Fluch und Segen: Beginnt eine Datei mit eine der 2-Byte-Sequenzen feff (Hex) oder fffe (Hex) gehen viele Programme stillschweigend davon aus, dass es sich beim Inhalt der Datei um Daten in UTF-16 handeln muss. Bei UTF-8 fehlt dagegen eine solche automatische Erkennungsmöglichkeit.
Trotz dieser Nachteile ist UTF-8 heute der De-Fakto-Standard für den Austausch und die Speicherung von Unicode-Daten. Beim Datenaustausch treten die Schwierigkeiten ja nur an den Schnittstellen auf, und können durch eine einmalige Konvertierung in performantere Formate für den internen Gebrauch leicht umgangen werden.
| Guido Flohr | Imperia AG | Impressum |