»Ohne Unterschied macht Gleichheit keinen Spaß.« – Dieter Hildebrandt (*1927)
4 Der Umgang mit Zeichenketten
4.1 Einzelne Zeichen behandeln 

4.1.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.
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) genannt wird. Es gibt 94 druckbare Zeichen (Buchstaben, Ziffern, Interpunktionszeichen), 33 nicht druckbare Kontrollzeichen (etwa den Tabulator und viele andere Zeichen, die bei Fernschreibern nützlich waren, aber heute uninteressant sind), und das Leerzeichen, das nicht als Kontrollzeichen gezählt wird. Am Anfang des ASCII-Alphabets stehen an den Positionen 0–31 Kontrollzeichen, an Stelle 32 folgt das Leerzeichen und anschließend alle druckbaren Zeichen. An der letzten Position, 127, wird ASCII von einem Kontrollzeichen abgeschlossen.
Die folgende Tabelle stammt aus dem Originalstandard von 1968 und gibt einen Überblick über die Position der Zeichen.
ISO/IEC 8859-1
An dem ASCII-Standard gab es zwischendurch Aktualisierungen, sodass einige Kontrollzeichen 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 und so weiter. Die Lösung war, statt einer 7-Bit-Kodierung, die 128 Zeichen unterbringen kann, einfach 8 Bit zu nehmen, womit 265 Zeichen kodiert werden können. Da in weiten Teilen der Welt das lateinische Alphabet genutzt wird, sollte diese Kodierung natürlich alle die Buchstaben zusammen mit einem Großteil aller diakritischen Zeichen (das sind etwa ü, á, à, ó, â, Å, Æ) umfassen. So setzte sich ein Standardisierungsgremium zusammen und schuf 1985 den ISO/IEC 8859-1-Standard, der 191 Zeichen beschreibt. 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 Kontrollzeichen – etwa der Tabulator oder das Zeilenumbruchzeichen – nicht dazu gehören. Von den 256 möglichen Positionen bleiben 65 Stellen frei. Das sind die Stellen 0 bis 31 sowie 127 von den ASCII-Kontrollzeichen 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 Kontrollzeichen aus dem ASCII-Standard an den Positionen 0–31 und 127. Somit steckt ASCII vollständig in ISO 8858-1, aber nur die druckbaren ASCII-Zeichen sind in ISO/IEC 8859-1. Auch die Stellen 128 bis 159 sind in ISO 8858-1 definiert, wobei es alles recht unbekannte Kontrollzeichen (wie Padding, Start einer Selektion, kein Umbruch) sind.
Windows-1252 *
Weil die Zeichen an der Stelle 128 bis 159 uninteressante Kontrollzeichen 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.
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, mathematische Zeichen und vieles mehr. Um das Problem zu lösen, hat sich das Unicode-Konsortium gebildet, um jedes 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 Dokumenten vereinfacht. So behält zum Beispiel »A« den Codepoint 65 von ISO 8859-1, den der Buchstabe wiederum von ASCII erbt. Die letzte Version des Unicode-Standards ist 5.1. Sie beschreibt ca. 107.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.
Unicode-Zeichenkodierung
Da es im Unicode-Standard 5.1 um die 107.000 Zeichen gibt, werden zur Kodierung eines Zeichens 4 Byte beziehungsweise 32 Bit verwendet. Ein Dokument, das Unicode 5.1-Zeichen enthält, wird dann einen Speicherbedarf von 4 × »Anzahl der Zeichen« besitzen. Wenn die Zeichen auf diese Weise kodiert werden, sprechen wie 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 der Unicode-5.1-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 folgende Beispiel [http://java.sun.com/developer/technicalArticles/Intl/Supplementary/ ] 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 |
00 01 04 00 |
|
UTF-16 |
00 41 |
00 DF |
6771 |
D801 DC00 |
|
UTF-8 |
41 |
C3 9F |
E6 9D B1 |
F0 90 90 80 |
Werden Texte ausgetauscht, sind diese ü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.
Standardmäßig ist ein char 2 Byte groß. Das heißt aber auch, dass ein Zeichen, das größer als 65.536 ist, irgendwie anders kodiert werden muss. Dazu muss ein »großes« Unicode-Zeichen durch zwei char zusammengesetzt werden. Dieses Pärchen wird Surrogate-Paar genannt. Unter Java 5 gab es an den String-Klassen einige Änderungen, sodass etwa eine Methode, die nach einem Zeichen sucht, nun nicht nur mit einem char parametrisiert ist, sondern auch mit int, und damit das Surrogate-Paar übergeben werden kann. In diesem Buch spielt das aber keine große Rolle, da diese Unicode-Zeichen 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 Ärgernissen. 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 sie etwa in XML- oder HTML-Dokumenten in den Text selbst geschrieben. Bei Unicode-UTF-16-Dokumenten gibt es eine andere Konvention, sodass sie mit den Hexwert 0xFEFF beginnen – das wird BOM (byte order mark) genannt und dient gleichzeitig als Indikator für die Byte-Reihenfolge. |
4.1.2 Die Character-Klasse 

Die im Kernpaket java.lang angesiedelte Klasse Character bietet eine große Anzahl statischer Methoden, die im Umgang mit einzelnen Zeichen interessant sind. Dazu gehören Methoden zum Testen, etwa ob ein Zeichen eine Ziffer, ein Buchstabe oder ein Sonderzeichen ist. Ihnen ist gemeinsam, dass sie alle mit der Vorsilbe is beginnen und ein boolean liefern. Dazu gesellen sich Methoden zum Konvertieren, etwa in Groß-/Kleinschreibung.
|
Beispiel für isDigit() und isLetter(): Listing 4.1 CharacterDemo.java, main() System.out.println( Character.isDigit( '0' ) ); // true System.out.println( Character.isDigit( '-' ) ); // false System.out.println( Character.isLetter( 'ß' ) ); // true System.out.println( Character.isLetter( '0' ) ); // false System.out.println( Character.isWhitespace( ' ' ) ); // true System.out.println( Character.isWhitespace( '-' ) ); // false |
Alle diese Methoden »wissen« über die Eigenschaften der einzelnen Unicode-Zeichen Bescheid. Und 0 bleibt ja immer eine Null, egal ob das Programm in Deutschland oder in der Mongolei ausgeführt wird, denn der Codepoint jedes Unicode-Zeichens ist immer der gleiche.
Oftmals finden die Testfunktionen Anwendung beim Ablaufen einer Zeichenkette.
|
Beispiel Eine Zeichenkette soll in Großbuchstaben umgesetzt beziehungsweise Weißraum soll zu einem Unterstrich werden: Listing 4.2 UppercaseWriter.java, main() char[] chars = { 'M', 'e', ' ', '2' };
for ( char c : chars
if ( Character.isWhitespace(
System.out.print( '_' );
else
System.out.print( Character.toUpperCase( c ) ); |
final class java.lang.Character
implements Serializable, Comparable<Character> |
- static boolean isDigit( char ch ) Handelt es sich um eine Ziffer zwischen 0 und 9?
- static boolean isLetter( char ch ) Handelt es sich um einen Buchstaben?
- static boolean isLetterOrDigit( char ch ) Handelt es sich um ein alphanumerisches Zeichen?
- static boolean isLowerCase( char ch ), boolean isUpperCase( char ch ) Ist es ein Klein- oder ein Großbuchstabe?
- static boolean isWhiteSpace( char ch ) Ist es ein Leerzeichen, Zeilenvorschub, Return oder Tabulator, also ein so genannter Weißraum (engl. white space) [Es wird Weißraum genannt, weil das ausgegebene Zeichen den Raum in der Regel weiß lässt, aber die Position der Ausgabe dennoch fortschreitet. ] , auch Leerraum genannt?
- static boolean isJavaIdentifierStart( char ch ) Ist es ein Java-Buchstabe, mit dem Bezeichner beginnen dürfen?
- static boolean isJavaIdentifierPart( char ch ) Ist es ein Java-Buchstabe oder eine Ziffer, der beziehungsweise die in der Mitte eines Bezeichners vorkommen darf?
- static boolean isTitleCase( char ch ) Sind es spezielle Zwei-Buchstaben-Paare mit gemischter Groß- und Kleinschreibung? Dies kommt etwa im Spanischen vor, wo »lj« für einen einzigen Buchstaben steht. In Überschriften erscheint dieses Paar als »Lj« und wird von dieser Methode als Sonderfall erkannt. Unter http://www.unicode.org/reports/tr21/tr21-5.html schreibt der Unicode-Standard die Konvertierung vor.
- static char toUpperCase( char ch ), static char toLowerCase( char ch ) Die statischen Methoden toUpperCase() und toLowerCase() liefern den passenden Groß- beziehungsweise Kleinbuchstaben zurück.
|
Hinweis Die Methoden toUpperCase() und toLowerCase() gibt es zweimal: Einmal als statische Methoden bei Character – dann nehmen sie genau ein char als Argument – und einmal als Objektmethoden auf String-Exemplaren. Vorsicht ist bei Character.toUpperCase('ß') geboten, denn das Ergebnis ist »ß«, anders als bei der String-Methode toUpperCase("ß"), die das Ergebnis »SS« liefert; einen String, der um eins verlängert ist. |
Die Character-Klasse besitzt ebenso eine Umwandlungsmethode für Ziffern bezüglich einer beliebigen Basis:
final class java.lang.Character
implements Serializable, Comparable<Character> |
- static int digit( char ch, int radix ) Liefert den numerischen Wert, den das Zeichen ch unter der Basis radix besitzt. Beispielsweise ist Character.digit('f', 16) gleich 15. Erlaubt ist jedes Zahlensystem mit einer Basis zwischen Character.MIN_RADIX (2) und Character.MAX_RADIX (36). Ist keine Umwandlung möglich, beträgt der Rückgabewert –1.
- static char forDigit( int digit, int radix ) Konvertiert einen numerischen Wert in ein Zeichen. Beispielsweise ist Character.for Digit(6, 8) gleich »6« und Character.forDigit(12, 16) ist »c«.
Es ist bedauerlich, dass der Radix immer mit angegeben werden muss, obwohl er in der Regel immer 10 ist. Eine überladene statische Methode wäre hier angebracht.
|
Beispiel Steht in einem Zeichen c zum Beispiel '3' und soll aus diesem die Ganzzahl 3 werden, so besteht die traditionelle Art darin, eine '0' abzuziehen. Die ASCII-Null '0' hat den char-Wert 48, '1' dann 49, bis '9' schließlich 57 erreicht. So ist logischerweise '3' – '0' = 51 – 48 = 3.) Die digit()-Methode ist dazu eine Alternative. Wir nutzen sie in einem kleinen Beispiel, um eine Zeichenkette mit Ziffern schließlich zu einer Ganzzahl zu konvertieren. Listing 4.3 CharacerDigitDemo.java, main() char[] chars = { '3', '4', '0' };
int result = 0;
for ( char c : chars )
{
result = result * 10 + Character.digit( c, 10 );
System.out.println( result );
}Die Ausgabe ist 3, 34 und 340. |






Jetzt bestellen





