6.6 Zeichenketten (Strings)
6.6 Zeichenketten (Strings)Zeichenketten werden in Java mit der Klasse String verwaltet. Sie sind Objekte.
Sie wurden bisher wie die Basistypen Character, Integer oder Float behandelt. Zeichenketten werden jedoch ähnlich wie Referenzen behandelt da Zeichenketten unterschiedlich lang sein können. Man kann Zeichenketten daher nicht die Zahlentypen mit fester Größe einfach in den Speicherbereich eines Objekts einbetten.
Zeichenketten werden bei Bedarf im Hauptspeicher (Heap) angelegt und referenziert. Die Referenz auf die Zeichenkette selbst hat eine konstante Größe.
Zeichenketten können mit einer direkten Zuweisung erzeugt werden. Sie können jedoch auch gleichwertig mit dem new() Operator und dem Konstruktor der Klasse String angelegt werden. Beide Varianten sind nur in der Syntax unterschiedlich. Der einzige Unterschied besteht darin, dass das Objekt "John" schon beim Laden der Klasse angelegt wird. Das Objekt "Doe" wird erst beim Aufruf des Befehls angelegt.
String a = "John"; String b = new String("Doe"); |
a sowie b sind Objektreferenzen nicht aber der direkte Datenbehälter!
Zeichenketten(Strings) können einfach mit dem plus "+" Operator verkettet werden. Der plus Operator konvertiert automatisch andere Basistypen in Zeichenketten bevor die neue verkettete Zeichenkette zugewiesen wird:
String a = "Das Ergebnis ist "; double pi = 3.1415; String b = "***"; String c = a + pi + b;
Tipp: Die Klasse String verfügt über eine ganze Reihe von hilfreichen Methoden. Hier eine Auswahl:
- (static) valueOf(beliebiger Typ): Konvertierung eines beliebigen Typs (Basistypen, Objekte, Felder etc.) in eine Zeichenkette
- length(): Länge der Zeichenkette
- toLowerCase(): Umwandlung in Kleinbuchstaben
- equalsIgnoreCase(String s): Vergleicht eine gegebene Zeichenkette mit einer Zeichenkette s ohne Groß- und Kleinschreibung zubeachten
Zeichenketten werden von der virtuellen Maschine als unveränderbare ("immutable") Objekte gehandhabt. Sie können daher nicht überschrieben oder verändert werden. Die Implikationen können am folgenden Beispiel gezeigt werden.
Schritt 1: Anlegen der Zeichenketten
String a = "John"; String b = a; String c = " Doe"; |
Schritt 2: Verkettung und Objektallokation
Eine Verkettung der Zeichenketten mit dem plus Operator ergibt das folgende Speicherabbild c = a + c; Die Variable c zeigt jetzt auf ein neues Objekt. Die Zeichenkette " Doe" ist eventuell nicht mehr referenziert und wird dann vielleicht irgendwann gelöscht. Bei der oben gezeigten Verkettung entstehen typischerweise sehr viele neue Objekte und es werden sehr viele Stringobjekte zum Löschen freigesetzt! |
Vergleich
Da ein Objekt vom Typ String eine Referenz auf eine Zeichenkette ist, muss man beim Vergleich von Zeichenketten unterscheiden, ob man die Referenz oder den Inhalt der Zeichenkette vergleichen möchte um Fehler zu vermeiden.
Der Javaübersetzer und das Javalaufzeitsystem speichern Literale wenn möglich nur einmal im Hauptspeicher. Konstante Literale die man zur Übersetzungszeit erkennen kann werden in Referenzen auf ein einziges Objekt zusammengefasst. Dynamisch erzeugte Zeichenketten werden in eigenen Objekten verwaltet. Die genaue Semantik ist in der Java Sprachspezifikation (3.te Version von 2005, Paragraph 3.10.5 String Literals) beschrieben.
Wichtig: Identische Zeichenketten haben nicht unbedingt die gleiche Objektidentität. Ein Vergleich mit Hilfe der Objektidentität ist daher im Normalfall nicht ratsam.
String a = "John"; String b = a; String c = "John"; String d = "Jo"+"hn"; // Der konstante Ausdruck // wird schon bei der Übersetzung aufgelöst! String e = new String("John"); |
das Speicherabbild zum Quellcode links: |
Ein Vergleich mit dem == Operator vergleicht nur die Adressen der referenzierten Objekte nicht aber deren Inhalt. Zum Vergleich der Inhalte wird die .equals() Methode benötigt. Siehe
if (a == b) System.out.println(" a und b zeigen auf das gleiche Objekt"); else System.out.println(" a und b zeigen nicht auf das gleiche Objekt"); if (a == c) System.out.println(" a und c zeigen auf das gleiche Objekt"); else System.out.println(" a und c zeigen nicht auf das gleiche Objekt"); if (a == d) System.out.println(" a und d zeigen auf das gleiche Objekt"); else System.out.println(" a und d zeigen nicht auf das gleiche Objekt"); if (a == e) System.out.println(" a und e zeigen auf das gleiche Objekt"); else System.out.println(" a und e zeigen nicht auf das gleiche Objekt"); if (a.equals(b)) System.out.println(" a und b sind gleiche Zeichenketten"); else System.out.println(" a und b sind nicht gleiche Zeichenketten"); if (a.equals(c)) System.out.println(" a und c sind gleiche Zeichenketten"); else System.out.println(" a und c sind nicht gleiche Zeichenketten"); if (a.equals(d)) System.out.println(" a und d sind gleiche Zeichenketten"); else System.out.println(" a und d sind nicht gleiche Zeichenketten"); if (a.equals(e)) System.out.println(" a und e sind gleiche Zeichenketten"); else System.out.println(" a und e sind nicht gleiche Zeichenketten");
Führt zum Ergebnis:
a und b zeigen auf das gleiche Objekt a und c zeigen auf das gleiche Objekt a und d zeigen auf das gleiche Objekt a und e zeigen nicht auf das gleiche Objekt a und b sind gleiche Zeichenketten a und c sind gleiche Zeichenketten a und d sind gleiche Zeichenketten a und e sind gleiche Zeichenketten
Zeichenkettenverwaltung im "Stringpool"
Der javac Übersetzer erkennt Literale schon beim Übersetzen einer Klasse und speichert sie nur einmal in der .class Datei mit dem Binärcode. Zur Laufzeit eines Javaprogramms wird die entsprechende Klasse bei der ersten Benutzung dynamisch geladen und die konstanten Zeichenketten (Literale) zu einem "Stringpool" hinzugefügt, wenn sie vorher noch nicht im Stringpool existierten. Dieses Verfahren minimiert den Speicherverbrauch und steigert den Durchsatz da Zeichenketten, in der Regel, kleine Objekte sind, die sonst den Freispeicher (Heap) dauerhaft belegen und fragmentieren.
Der Stringpool ist ein spezialisierter Bereich des Freispeichers. Objekte im allgemeinen Freispeicher und im Stringpool verhalten sich gleich. Der einzige Unterschied besteht darin, dass alle Literale im Stringpool nur genau einmal vorkommen.
Wird eine Zeichenkette dynamisch mit new String() angelegt so wird sie wie ein normales Objekt auf dem Freispeicher (Heap) angelegt und nicht im Stringpool. Die Klasse String hat jedoch eine Methode String.intern() die immer auf das kanonisierte Literal im Stringpool zeigt.
Der Zusammenhang zwischen Stringobjekten im Freispeicher wird durch das folgende Beispielprogramm deutlich:
String a = new String("Test"); String b = a.intern(); String c = new String("Test"); String d = c.intern(); String e = "Test"; if (a == c) System.out.println ("a,c sind die gleichen Objekte"); if (b == d) System.out.println ("b,d sind die gleichen Objekte"+ " im Stringpool"); if (e == d) System.out.println ("e,d sind die gleichen Objekte"+ " im Stringpool"); Ausgabe: b,d sind die gleichen Objekte im Stringpool d,e sind die gleichen Objekte im Stringpool |
Effiziente Zeichenverwaltung mit der Klasse Stringbuffer
Die Klasse StringBuffer dient dem effizienten Handhaben von Zeichenketten. Ihre wesentlichen Eigenschaften sind:
- "Threadsave" beim parallelen Ausführen von Threads in einem Javaprogramm werden Zeichenketten nicht versehentlich zerstört. Die Klasse ist hiermit sicher jedoch langsamer als eine nicht threadsichere Klasse (aber immer noch schneller als die Klasse String!)
- Die Zeichenketten sind veränderbar
Wichtige Methoden Methoden der Klasse Stringbuffer sind:
- append(beliebiger Typ): Fügt einen beliebigen Typ in Form einer Zeichenkette am Ende an
- insert(offset, beliebiger Typ): Fügt einen beliebigen Typ an der Stelle offset ein.
Die Klasse StringBuffer verfügt über viele weitere Methoden zur Manipulation von Zeichenketten die in der API Dokumentation beschrieben werden.
Tipp: Seit JDK 5.0 steht die Klasse StringBuilder für Anwendungen die extreme Leistung benötigen zur Verfügung. Sie bietet jedoch keine Synchronisation zwischen Threads.
- 8090 views
Schwer verständlich ausgedrückt
Bitte erläutern Sie die Stelle mit "[...]dynamisch geladen[...]" sowie die Stelle mit [...]auf das kanonisierte Literal [...].
Was bedeutet dynamisch geladen? Was hat es mit dem kanonisierten Literal auf sich, also was heißt kanonisiert?
Können Sie die Methode String.intern() genauer erklären bitte?
Danke im Voraus und lG
Versuch einer informellen Erklärung
Motivation für diese Optimierung
Die Kosten zum Löschen sind also in Relation zum gewonnenen Speicherplatz hoch. Man versucht ausserdem viele dieser konstanten Objekte in einem gemeinsamen Speicherbereich eng zu packen. Dadurch wird das cachen von Hauptspeicherseiten im Prozessor effizienter.
Erste Überlegung:
Die zweite Überlegung: Wann werden dem Laufzeitsystem Zeichenketten bekannt gemacht: