5 Der Umgang mit Zeichenketten
»Ohne Unterschied macht Gleichheit keinen Spaß.«
– Dieter Hildebrandt (1927–2013)
5.1 Von ASCII über ISO-8859-1 zu Unicode
Die Übertragung von Daten spielte in der IT schon immer eine zentrale Rolle. Daher haben sich unterschiedliche Standards herausgebildet. Sie sind Gegenstand der nächsten Abschnitte.
5.1.1 ASCII
Um Dokumente austauschen zu können, führte die American Standards Association im Jahr 1963 eine 7-Bit-Kodierung ein, die ASCII (von American Standard Code for Information Interchange) genannt wird.
ASCII gibt jedem der 128 Zeichen (mehr Zeichen passen in 7 Bit nicht hinein) eine eindeutige Position, die Codepoint (Codeposition) heißt. Es gibt 94 druckbare Zeichen (Buchstaben, Ziffern, Interpunktionszeichen), 33 nicht druckbare Steuerzeichen (etwa den Tabulator und viele andere Zeichen, die bei Fernschreibern nützlich waren, aber heute uninteressant sind) und das Leerzeichen, das nicht als Steuerzeichen zählt.
Am Anfang des ASCII-Alphabets stehen an den Positionen 0–31 Steuerzeichen, an Stelle 32 folgt das Leerzeichen, und anschließend kommen alle druckbaren Zeichen. An der letzten Position, 127, schließt ein Steuerzeichen (»delete«, früher »rubout«) die ASCII-Tabelle ab.
Die Tabelle in Abbildung 5.1 stammt aus dem Originalstandard von 1968 und gibt einen Überblick über die Position der Zeichen.
5.1.2 ISO/IEC 8859-1
An dem ASCII-Standard gab es zwischendurch Aktualisierungen, sodass einige Steuerzeichen entfernt wurden, doch in 7 Bit konnten nie alle länderspezifischen Zeichen untergebracht werden. Wir in Deutschland haben Umlaute, die Russen haben ein kyrillisches Alphabet, die Griechen Alpha und Beta usw. Die Lösung war, statt einer 7-Bit-Kodierung, die 128 Zeichen unterbringen kann, einfach 8 Bit zu nehmen, womit 256 Zeichen kodiert werden können. Da weite Teile der Welt das lateinische Alphabet nutzen, sollte diese Kodierung natürlich alle diese Buchstaben zusammen mit einem Großteil aller diakritischen Zeichen (das sind etwa ü, á, à, ó, â, Å, Æ) umfassen. So setzte sich ein Standardisierungsgremium zusammen und schuf 1985 den Standard ISO/IEC 8859-1, der 191 Zeichen beschreibt, was die Kodierung für eine große Anzahl von Sprachen wie Deutsch, Spanisch, Französisch oder Afrikaans nützlich macht. Die Zeichen aus dem ASCII-Alphabet behalten ihre Positionen. Wegen der lateinischen Buchstaben hat sich die informelle Bezeichnung Latin-1 als Alternative zu ISO/IEC 8859-1 etabliert.
Alle Zeichen aus ISO/IEC 8859-1 sind druckbar, sodass alle Steuerzeichen – etwa der Tabulator oder das Zeilenumbruchzeichen – nicht dazugehören. Von den 256 möglichen Positionen bleiben 65 Stellen frei. Das sind die Stellen 0 bis 31 sowie 127 von den ASCII-Steuerzeichen und zusätzlich 128 bis 159.
ISO 8859-1
Da es kaum sinnvoll ist, den Platz zu vergeuden, gibt es eine Erweiterung des ISO/IEC-8859-1-Standards, die unter dem Namen ISO 8859-1 (also ohne IEC) geläufig ist. ISO 8859-1 enthält alle Zeichen aus ISO/IEC 8859-1 sowie die Steuerzeichen aus dem ASCII-Standard an den Positionen 0–31 und 127. Somit steckt ASCII vollständig in ISO 8859-1, aber nur die druckbaren ASCII-Zeichen sind in ISO/IEC 8859-1. Auch die Stellen 128 bis 159 sind in ISO 8859-1 definiert, wobei es alles recht unbekannte Steuerzeichen (wie Padding, Start einer Selektion, kein Umbruch) sind.
Windows-1252 *
Weil die Zeichen an der Stelle 128 bis 159 uninteressante Steuerzeichen sind, belegt Windows sie mit Buchstaben und Interpunktionszeichen und nennt die Kodierung Windows-1252. An Stelle 128 liegt etwa das €-Zeichen, an 153 das ™-Symbol. Diese Neubelegung der Plätze 128 bis 159 hat sich mittlerweile auch in der Nicht-Windows-Welt etabliert, sodass das, was im Web als ISO-8859-1 deklariert ist, heute die Symbole aus den Codepoints 128 bis 159 enthalten kann und von Browsern so dargestellt wird.
5.1.3 Unicode
Obwohl Latin-1 für die »großen« Sprachen alle Zeichen mitbrachte, fehlen Details, wie Ő, ő, ű, Ű für das Ungarische, das komplette griechische Alphabet, die kyrillischen Buchstaben, chinesische und japanische Zeichen, indische Schrift, mathematische Zeichen und vieles mehr. Um das Problem zu lösen, bildete sich das Unicode-Konsortium, um jedes elementare Zeichen der Welt zu kodieren und ihm einen eindeutigen Codepoint zu geben. Unicode enthält alle Zeichen aus ISO 8859-1, was die Konvertierung von vielen Dokumenten vereinfacht. So behält zum Beispiel »A« den Codepoint 65 von ISO 8859-1, den der Buchstabe wiederum von ASCII erbt. Unicode ist aber viel mächtiger als ASCII oder Latin-1: Die letzte Version des Unicode-Standards, Unicode 9, umfasst 135 Schriftsysteme und über 120.000 Zeichen.
Wegen der vielen Zeichen ist es unpraktisch, diese dezimal anzugeben, sodass sich die hexadezimale Angabe durchgesetzt hat. Der Unicode-Standard nutzt das Präfix »U+«, gefolgt von Hexadezimalzahlen. Der Buchstabe »A« ist dann U+0041. Prinzipiell umfasst der Bereich 1.114.112 mögliche Codepunkte, von U+0000 bis U+10FFFF.
Unicode-Tabellen unter Windows *
Unter Windows legt Microsoft das nützliche Programm charmap.exe für eine Zeichentabelle bei, mit der jede Schriftart auf ihre installierten Zeichen untersucht werden kann. Praktischerweise zeigt die Zeichentabelle auch gleich die Position in der Unicode-Tabelle an.
Unter Erweiterte Ansicht lassen sich mit Gruppieren nach in einem neuen Dialog Unicode-Unterbereiche auswählen, wie etwa Währungszeichen oder unterschiedliche Sprachen. Im Unterbereich Latin finden sich zum Beispiel die Zeichen aus der französischen Schrift (etwa »Ç« mit Cedille unter U+00C7) und der spanischen Schrift (»ñ« mit Tilde unter U+00F1), und bei Allg. Interpunktionszeichen findet sich das umgedrehte (invertierte) Fragezeichen bei U+00BF.
Anzeige der Unicode-Zeichen *
Der Unicode-Standand definiert nur die Position von Zeichen, aber nicht, wie sie grafisch aussehen. Die grafische Darstellung eines Zeichens nennt sich Glyphe, auch Schriftzeichen genannt. Schriftarten wie Times oder Arial enthalten Darstellungen für die Glyphen. Doch unterschiedliche Schriftarten enthalten nur Teilmengen aller Unicode-Zeichen; viele freie Schriftarten enthalten zum Beispiel überhaupt keine asiatischen Zeichen.
Auch die Darstellung der Zeichen – besonders auf der Konsole – ist auf einigen Plattformen noch ein Problem. Die Unterstützung für die Standardzeichen des ASCII-Alphabets ist dabei weniger ein Problem als die Sonderzeichen, die der Unicode-Standard definiert. Ein Versuch, zum Beispiel den Smiley auf der Standardausgabe auszugeben, scheitert oft an der Fähigkeit des Terminals bzw. der Shell. Hier ist eine spezielle Shell notwendig, die aber bei den meisten Systemen noch in der Entwicklung ist. Und auch bei grafischen Oberflächen ist die Integration noch verbesserungswürdig. Es wird Aufgabe der Betriebssystementwickler bleiben, dies zu ändern.[ 128 ](Mit veränderten Dateiströmen lässt sich dies etwas in den Griff bekommen. So kann man beispielsweise mit einem speziellen OutputStream-Objekt eine Konvertierung für die Windows-NT-Shell vornehmen, sodass auch dort die Sonderzeichen erscheinen. )
[»] Hinweis
Obwohl Java intern alle Zeichenfolgen in Unicode kodiert, ist es ungünstig, Klassennamen zu wählen, die Unicode-Zeichen enthalten. Einige Dateisysteme speichern die Namen im alten 8‐Bit-ASCII-Zeichensatz ab, sodass Teile des Unicode-Zeichens verloren gehen.
5.1.4 Unicode-Zeichenkodierung
Da es im aktuellen Unicode-Standard 12 mehr als 137.000 Zeichen gibt, werden zur Kodierung eines Zeichens 4 Byte bzw. 32 Bit verwendet. Ein Dokument, das Unicode-Zeichen enthält, besitzt dann einen Speicherbedarf von 4 × (Anzahl der Zeichen). Wenn die Zeichen auf diese Weise kodiert werden, sprechen wir von einer UTF-32-Kodierung.
Für die meisten Texte ist UTF-32 reine Verschwendung, denn besteht der Text aus nur einfachen ASCII-Zeichen, sind 3 Byte gleich 0. Gesucht ist eine Kodierung, die die allermeisten Texte kompakt kodieren kann, aber dennoch jedes Unicode-Zeichen zulässt. Zwei Kodierungen sind üblich: UTF-8 und UTF-16. UTF-8 kodiert ein Zeichen entweder in 1, 2, 3 oder 4 Byte, UTF-16 in 2 Byte oder 4 Byte. Das Beispiel aus Tabelle 5.1 zeigt die Kodierung für die Buchstaben »A« und »ß«, für das chinesische Zeichen für Osten und für ein Zeichen aus dem Deseret, einem phonetischen Alphabet.
Glyph | A | ß | ||
---|---|---|---|---|
Unicode-Codepoint | U+0041 | U+00DF | U+6771 | U+10400 |
UTF-32 | 00000041 | 000000DF | 00006771 | 00010400 |
UTF-16 | 0041 | 00DF | 6771 | D801 DC00 |
UTF-8 | 41 | C3 9F | E6 9D B1 | F0 90 90 80 |
Werden Texte ausgetauscht, sind sie üblicherweise UTF-8-kodiert. Bei Webseiten ist das ein guter Standard. UTF-16 ist für Dokumente seltener, wird aber häufiger als interne Textrepräsentation genutzt. So verwenden zum Beispiel die JVM und die .NET-Laufzeitumgebung intern UTF-16.
5.1.5 Escape-Sequenzen/Fluchtsymbole
Um spezielle Zeichen, etwa den Zeilenumbruch oder Tabulator, in einen String oder char setzen zu können, stehen Escape-Sequenzen[ 129 ](Nicht alle aus C stammenden Escape-Sequenzen finden sich auch in Java wieder. Es gibt kein '\a' (Alert), kein '\v' (vertikaler Tabulator) und kein '\?' (Fragezeichen). ) (zu Deutsch: Fluchtsymbole) zur Verfügung.
Zeichen | Bedeutung |
---|---|
\b | Rückschritt (Backspace) |
\n | Zeilenschaltung (Newline) |
\f | Seitenumbruch (Formfeed) |
\r | Wagenrücklauf (Carriage Return) |
\t | horizontaler Tabulator |
\" | doppeltes Anführungszeichen |
\' | einfaches Anführungszeichen |
\\ | Backslash |
[zB] Beispiel
Zeichenvariablen mit Initialwerten und Sonderzeichen:
char theLetterA = 'a',
doubleqoute = '"',
singlequote = '\'',
newline = '\n';
Die Fluchtsymbole sind für Zeichenketten die gleichen. Auch dort können bestimmte Zeichen mit Escape-Sequenzen dargestellt werden:
String s = "Er 'fragte': \"Wer lispelt wie Katja Burkard?\"";
String filename = "C:\\Dokumente\\Siemens\\Schweigegeld.doc";
5.1.6 Schreibweise für Unicode-Zeichen und Unicode-Escapes
Da ein Java-Compiler alle Eingaben als Unicode verarbeitet, kann er grundsätzlich Quellcode mit deutschen Umlauten, griechischen Symbolen und chinesischen Schriftzeichen verarbeiten. Allerdings ist es gut, zu überlegen, ob ein Programm direkt Unicode-Zeichen enthalten sollte, denn Editoren haben mit Unicode-Zeichen oft ihre Schwierigkeiten – genauso wie Dateisysteme.
Unicode-Escapes
Beliebige Unicode-Zeichen lassen sich für den Compiler über Unicode-Escapes schreiben. Im Quellcode steht dann \uxxxx, wobei x eine hexadezimale Ziffer ist – also 0…9, A…F (bzw. a…f). Diese sechs ASCII-Zeichen, die das Unicode-Zeichen beschreiben, lassen sich in jedem ASCII-Texteditor schreiben, sodass kein Unicode-fähiger Editor nötig ist. Unicode-Zeichen für deutsche Sonderzeichen sind folgende:
Zeichen | Unicode |
---|---|
Ä, ä | \u00c4, \u00e4 |
Ö, ö | \u00d6, \u00f6 |
Ü, ü | \u00dc, \u00fc |
ß | \u00df |
[+] Tipp
Sofern sich die Sonderzeichen und Umlaute auf der Tastatur befinden, sollten keine Unicode-Kodierungen Verwendung finden. Der Autor von Quelltext sollte seine Leser nicht zwingen, eine Unicode-Tabelle zur Hand zu haben. Die Alternativdarstellung lohnt sich daher nur, wenn der Programmtext bewusst unleserlich gemacht werden soll. Bezeichner sollten in der Regel aber so gut wie immer in Englisch geschrieben werden, sodass höchstens Unicode-Escapes bei Zeichenketten vorkommen.
[zB] Beispiel
Zeige Pi und in einem GUI-Dialog einen Grinsemann:
System.out.println( "Pi: \u03C0" ); // Pi: π
javax.swing.JOptionPane.showMessageDialog( null, "\u263A" );
Der beliebte Smiley ist als Unicode unter \u263A (WHITE SMILING FACE) bzw. unter \u2639 (WHITE FROWNING FACE) definiert. Das Euro-Zeichen € ist unter \u20ac zu finden.
Die Unicode-Escape-Sequenzen sind also an beliebiger Stelle erlaubt, aber wirklich nützlich ist das eher für Zeichenketten. Im Prinzip ist es kein Problem, Bezeichner aus dem erlaubten Unicode-Alphabet zu nutzen.
[zB] Beispiel
Deklariere und initialisiere eine Variable π:
double \u03C0 = 3.141592653589793;
Statt mit den herkömmlichen Buchstaben und Ziffern kann natürlich alles gleich in der \u-Schreibweise in den Editor gehackt werden, doch das ist nur dann sinnvoll, wenn Quellcode versteckt werden soll.
Beim Compiler kommt intern alles als Unicode-Zeichenstrom an, egal, ob wir in eine Datei
\u0063\u006C\u0061\u0073\u0073\u0020\u0041\u0020\u007B\u007D
setzen oder:
class A {}
Wichtig zu verstehen ist, dass so etwas wie \u000d im Code identisch mit einem Zeilenumbruch ist. Den Versuch, im Java-Quellcode eine Zeichenkette "\u000d" unterzubringen, bestraft der Compiler mit einem Fehler, denn es wäre identisch mit
"
"
System.out.println("\u0009"); klappt aber durchaus und gibt einen Tabulator aus. Das Unicode-Zeichen \uffff ist nicht definiert und kann bei Zeichenketten als Ende-Symbol verwendet werden.
Die Enkodierung des Quellcodes festlegen *
Enthält Quellcode Nicht-ASCII-Zeichen (wie Umlaute), dann ist das natürlich gegenüber der \u-Schreibweise ein Nachteil, denn so spielt das Dateiformat eine große Rolle. Es kann passieren, dass Quellcode in einer Zeichenkodierung abgespeichert wurde (etwa UTF-8), aber ein anderer Rechner eine andere Zeichenkodierung nutzt (vielleicht Latin-1) und den Java-Quellcode nun einlesen möchte. Das kann ein Problem werden, denn standardmäßig liest der Compiler den Quellcode in der Kodierung ein, die er selbst hat, denn an einer Textdatei ist die Kodierung nicht abzulesen. Liegt der Quellcode in einem anderen Format vor, konvertiert ihn der Compiler unbeabsichtigt in ein falsches, nicht entsprechendes Unicode-Format. Um das Problem zu lösen, gibt es folgenden Ansatz: Dem javac-Compiler kann mit dem Schalter -encoding die Kodierung mitgegeben werden, in der der Quellcode vorliegt. Liegt etwa der Quellcode in UTF-8 vor, ist aber auf dem System mit dem Compiler die Kodierung Latin-1 eingestellt, dann muss für den Compiler der Schalter -encoding UTF-8 gesetzt sein. Details dazu gibt es unter http://docs.oracle.com/en/java/javase/14/docs/specs/man/javac.html. Wenn allerdings unterschiedliche Dateien in unterschiedlichen Formaten vorliegen, dann hilft die globale Einstellung nichts.
5.1.7 Java-Versionen gehen mit dem Unicode-Standard Hand in Hand *
In den letzten Jahren hat sich der Unicode-Standard erweitert, und Java ist den Erweiterungen gefolgt:
Java-Version | Unicode-Version | Anzahl Zeichen |
---|---|---|
1.0 | 1.1.5 | 34.233 |
1.1 | 2.0 | 38.950 |
1.1.7, 1.2, 1.3 | 2.1 | 38.952 |
1.4 | 3.0 | 49.259 |
5, 6 | 4.0 | 96.447 |
7 | 6.0 | 109.242 |
8 | 6.2 | 110.182 |
9 | 8 | 120.737 |
11 | 10 | 136.690 |
12 | 11 | 137.374 |
13 | 12.1 | 137.928 |
Die Java-Versionen von 1.0 bis 1.4 nutzten einen Unicode-Standard, der für jedes Zeichen 16 Bit reserviert. So legt Java jedes Zeichen in 2 Byte ab und ermöglicht die Kodierung von mehr als 65.000 Zeichen aus dem Bereich U+0000 bis U+FFFF. Der Bereich heißt auch BMP (Basic Multilingual Plane). Java 5 unterstützte erstmalig den Unicode-4.0-Standard, der 32 Bit (also 4 Byte) für die Abbildung eines Zeichens nötig macht. Doch mit dem Wechsel auf Unicode 4 wurde nicht die interne Länge für ein char-Zeichen angehoben, sondern es bleibt dabei, dass ein char 2 Byte groß ist und der Bereich 0x0 bis 0xFFFF ist. Das heißt aber auch, dass Zeichen, die größer als 65.536 sind, irgendwie anders kodiert werden müssen. Der Trick ist, ein »großes« Unicode-Zeichen aus zwei chars zusammenzusetzen. Dieses Pärchen aus zwei 16-Bit-Zeichen heißt Surrogate-Paar. Sie bilden in der UTF-16-Kodierung ein Unicode-4.0-Zeichen. Diese Surrogate-Paare vergrößern den Bereich der Basic Multilingual Plane. Hätten wir char als echtes Objekt, könnte es wohl besser intern verstecken, wie viele Bytes die Speicherung wirklich benötigt. Aber Java musste ja unbedingt primitive Datentypen haben …
Mit der Einführung von Unicode 4 unter Java 5 gab es an den Klassen für Zeichen- und Zeichenkettenverarbeitung einige Änderungen, sodass etwa eine Methode, die nach einem Zeichen sucht, nun nicht nur mit einem char parametrisiert ist, sondern auch mit int und der Methode damit auch ein Surrogate-Paar übergeben werden kann. In diesem Buch spielt das aber keine Rolle, da Unicode-Zeichen aus den höheren Bereichen (etwa für die phönizische Schrift, die im Unicode-Block U+10900 bis U+1091F liegt – also kurz hinter 65.536, was durch 2 Byte abbildbar ist) nur für eine ganz kleine Gruppe von Interessenten wichtig sind.
[»] Entwicklerfrust
Die Abbildung eines Zeichens auf eine Position übernimmt eine Tabelle, die sich Codepage nennt. Nur gibt es unterschiedliche Abbildungen der Zeichen auf Positionen, und das führt zu Problemen beim Dokumentenaustausch. Denn wenn eine Codepage die Tilde »~« auf Position 161 setzt und eine andere Codepage das »ß« auch auf Position 161 anordnet, dann führt das zwangsläufig zu Konflikten. Daher muss beim Austausch von Textdokumenten immer ein Hinweis mitgegeben werden, in welchem Format die Texte vorliegen. Leider unterstützen die Betriebssysteme aber solche Meta-Angaben nicht, und so werden diese etwa in XML- oder HTML-Dokumenten in den Text selbst geschrieben. Bei Unicode-UTF-16-Dokumenten gibt es eine andere Konvention, sodass sie mit dem Hexwert 0xFEFF beginnen – das wird BOM (Byte Order Mark) genannt und dient gleichzeitig als Indikator für die Byte-Reihenfolge.