Parallelität
Java wurde von Anfang an unter Berücksichtigung der Parallelität definiert. Wie schon oft erwähnt, sind geteilte Variablen problematisch. Eine Sache kann eine andere hinter dem Rücken eines anderen Threads ändern, ohne dass dieser Thread davon Kenntnis hat.
Es gibt eine Vielzahl von Multithread-C ++ - Fehlern, die aufgrund eines gemeinsam genutzten Strings aufgetreten sind. Ein Modul hielt es für sicher, Änderungen vorzunehmen, wenn ein anderes Modul im Code einen Zeiger darauf gespeichert hatte und erwartete, dass dieser gleich bleibt.
Die "Lösung" für dieses Problem besteht darin, dass jede Klasse eine defensive Kopie der veränderlichen Objekte erstellt, die an sie übergeben werden. Für veränderbare Zeichenfolgen ist dies O (n), um die Kopie zu erstellen. Bei unveränderlichen Zeichenfolgen ist das Erstellen einer Kopie O (1), da es sich nicht um eine Kopie handelt, sondern um dasselbe Objekt, das nicht geändert werden kann.
In einer Multithread-Umgebung können unveränderliche Objekte immer sicher miteinander geteilt werden. Dies führt insgesamt zu einer Verringerung der Speichernutzung und verbessert das Zwischenspeichern von Speicher.
Sicherheit
Oft werden Zeichenfolgen als Argumente an Konstrukteure weitergegeben - Netzwerkverbindungen und Protokolle sind die beiden, die am einfachsten in den Sinn kommen. Die Möglichkeit, dies zu einem unbestimmten Zeitpunkt später in der Ausführung zu ändern, kann zu Sicherheitsproblemen führen (die Funktion glaubte, eine Verbindung zu einem Computer herzustellen, wurde jedoch zu einem anderen umgeleitet, aber alles im Objekt scheint mit dem ersten verbunden zu sein). es ist sogar die gleiche Zeichenfolge).
In Java kann man Reflection verwenden - und die Parameter dafür sind Strings. Die Gefahr, dass eine Zeichenfolge übergeben wird, die auf dem Weg zu einer anderen reflektierenden Methode geändert werden kann. Das ist sehr schlecht.
Schlüssel zum Hash
Die Hash-Tabelle ist eine der am häufigsten verwendeten Datenstrukturen. Die Schlüssel zur Datenstruktur sind sehr oft Zeichenfolgen. Unveränderliche Zeichenfolgen bedeuten, dass (wie oben) die Hash-Tabelle nicht jedes Mal eine Kopie des Hash-Schlüssels erstellen muss. Wenn Strings veränderbar wären und die Hash-Tabelle dies nicht schafft, könnte der Hash-Schlüssel in einiger Entfernung geändert werden.
Die Art und Weise, wie das Objekt in Java funktioniert, ist, dass alles einen Hash-Schlüssel hat (auf den über die Methode hashCode () zugegriffen wird). Ein unveränderlicher String bedeutet, dass der Hashcode zwischengespeichert werden kann. In Anbetracht der Häufigkeit, mit der Zeichenfolgen als Schlüssel für einen Hash verwendet werden, wird die Leistung erheblich gesteigert (anstatt dass der Hashcode jedes Mal neu berechnet werden muss).
Teilstrings
Indem der String unveränderlich ist, ist auch das zugrunde liegende Zeichenarray, das die Datenstruktur unterstützt, unveränderlich. Dadurch können bestimmte Optimierungen an der substring
Methode vorgenommen werden (dies ist nicht unbedingt der Fall - es können auch Speicherverluste auftreten).
Wenn Sie tun:
String foo = "smiles";
String bar = foo.substring(1,5);
Der Wert von bar
ist 'Meile'. Beide foo
und bar
können jedoch durch dasselbe Zeichenarray gesichert werden, wodurch die Instanziierung von mehr Zeichenarrays reduziert oder kopiert wird - nur mit unterschiedlichen Start- und Endpunkten in der Zeichenfolge.
foo | | (0, 6)
vv
lächelt
^^
bar | | (fünfzehn)
Der Nachteil davon (der Speicherverlust) ist, dass, wenn man eine 1k lange Zeichenfolge hätte und die Teilzeichenfolge des ersten und zweiten Zeichens nehmen würde, diese ebenfalls durch das 1k lange Zeichenarray unterstützt würde. Dieses Array würde auch dann im Speicher verbleiben, wenn die ursprüngliche Zeichenfolge, die den Wert des gesamten Zeichen-Arrays enthielt, fehlerhaft erfasst worden wäre.
Dies ist in String aus JDK 6b14 zu sehen (der folgende Code stammt aus einer GPL v2-Quelle und wird als Beispiel verwendet).
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
Beachten Sie, dass die Teilzeichenfolge den String-Konstruktor auf Paketebene verwendet, bei dem das Array nicht kopiert werden muss und der viel schneller ist (möglicherweise auf Kosten einiger großer Arrays - obwohl auch keine großen Arrays dupliziert werden).
Beachten Sie, dass der obige Code für Java 1.6 ist. Die Art und Weise, wie der Konstruktor der Teilzeichenfolge implementiert wird, wurde mit Java 1.7 geändert ( siehe Änderungen an der internen Darstellung von Zeichenfolgen in Java 1.7.0_06)
. Java wurde wahrscheinlich nicht als eine Sprache mit vielen Zeichenfolgenmanipulationen angesehen, und daher war die Leistungssteigerung für eine Teilzeichenfolge eine gute Sache. Bei riesigen XML-Dokumenten, die in Strings gespeichert sind, die niemals erfasst werden, wird dies zu einem Problem. String
Daher wird die Verwendung desselben zugrunde liegenden Arrays mit einer Teilzeichenfolge geändert , sodass das größere Zeichenarray schneller erfasst werden kann.
Missbrauche den Stapel nicht
Man könnte den Wert der Zeichenfolge anstelle des Verweises auf die unveränderliche Zeichenfolge übergeben, um Probleme mit der Veränderbarkeit zu vermeiden. Wenn Sie jedoch große Zeichenfolgen auf dem Stapel ablegen, wird dies dem System missbräuchlich. (Legen Sie ganze XML-Dokumente als Zeichenfolgen auf den Stapel und nehmen Sie sie dann ab oder leiten Sie sie weiter.)
Die Möglichkeit der Deduplizierung
Zugegeben, dies war keine anfängliche Motivation dafür, warum Strings unveränderlich sein sollten, aber wenn man sich die Gründe dafür ansieht, warum unveränderliche Strings eine gute Sache sind, sollte man dies mit Sicherheit berücksichtigen.
Jeder, der ein bisschen mit Strings gearbeitet hat, weiß, dass er Erinnerungen lutschen kann. Dies gilt insbesondere dann, wenn Sie Daten aus Datenbanken abrufen, die eine Weile in der Nähe bleiben. Oftmals sind diese Stiche immer wieder dieselbe Saite (einmal für jede Reihe).
Viele große Java-Anwendungen haben derzeit einen Engpass im Arbeitsspeicher. Messungen haben ergeben, dass ungefähr 25% des Java-Heap-Livedatensatzes in diesen Anwendungstypen von String-Objekten verbraucht wird. Etwa die Hälfte dieser String-Objekte sind Duplikate, wobei Duplikate bedeuten, dass string1.equals (string2) wahr ist. Doppelte String-Objekte auf dem Heap zu haben, ist im Wesentlichen nur eine Verschwendung von Speicher. ...
Mit Java 8 Update 20 wird JEP 192 (oben zitierte Motivation) implementiert, um dies zu beheben. Ohne näher auf die Funktionsweise der String-Deduplizierung einzugehen, ist es wichtig, dass die Strings selbst unveränderlich sind. Sie können StringBuilder nicht deduplizieren, da sie sich ändern können und Sie nicht möchten, dass jemand etwas unter Ihnen ändert. Unveränderliche Zeichenfolgen (im Zusammenhang mit diesem Zeichenfolgenpool) bedeuten, dass Sie durchgehen können. Wenn Sie zwei identische Zeichenfolgen finden, können Sie eine Zeichenfolgenreferenz auf die andere verweisen und den Garbage Collector die neu nicht verwendete Zeichenfolge verwenden lassen.
Andere Sprachen
Ziel C (welches vor Java liegt) hat NSString
und NSMutableString
.
C # und .NET haben dieselbe Entwurfsauswahl getroffen, bei der die Standardzeichenfolge unveränderlich ist.
Lua- Saiten sind ebenfalls unveränderlich.
Python auch.
In der Vergangenheit haben Lisp, Scheme, Smalltalk alle die Zeichenfolge interniert und müssen sie daher unveränderlich sein. Modernere dynamische Sprachen verwenden oft Strings in irgendeiner Art und Weise, dass sie sein unveränderlich erfordert (es ist nicht ein sein kann String , aber es ist unveränderlich).
Fazit
Diese gestalterischen Überlegungen wurden immer wieder in einer Vielzahl von Sprachen angestellt. Es besteht allgemeiner Konsens darüber, dass unveränderliche Zeichenfolgen trotz aller Ungeschicklichkeit besser sind als die Alternativen und zu besserem Code (weniger Bugs) und insgesamt schnelleren ausführbaren Dateien führen.