Ich denke, der wichtigste Punkt, den Sie hier verstehen sollten, ist die Unterscheidung zwischen StringJava-Objekt und seinem Inhalt - char[]im privaten valueBereich . Stringist im Grunde ein Wrapper um ein char[]Array, der es einkapselt und es unmöglich macht, es zu ändern, damit das Stringunveränderlich bleibt. Die StringKlasse merkt sich auch, welche Teile dieses Arrays tatsächlich verwendet werden (siehe unten). Dies alles bedeutet, dass Sie zwei verschiedene StringObjekte (ziemlich leicht) haben können, die auf dasselbe zeigen char[].
Ich werde Ihnen einige Beispiele zeigen, zusammen mit hashCode()jedem Stringund hashCode()internem char[] valueBereich (ich es nenne Text aus Zeichenfolge zu unterscheiden). Zum Schluss zeige ich die javap -c -verboseAusgabe zusammen mit dem konstanten Pool für meine Testklasse. Bitte verwechseln Sie den Klassenkonstantenpool nicht mit dem String-Literal-Pool. Sie sind nicht ganz gleich. Siehe auch Grundlegendes zur Ausgabe von javap für den konstanten Pool .
Voraussetzungen
Zum Testen habe ich eine solche Dienstprogrammmethode erstellt, die die StringKapselung unterbricht:
private int showInternalCharArrayHashCode(String s) {
final Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
return value.get(s).hashCode();
}
Es wird Druck hashCode()von char[] valueeffektiv helfen uns , ob diese besondere verstehen StringPunkte auf den gleichen char[]Text oder nicht.
Zwei String-Literale in einer Klasse
Beginnen wir mit dem einfachsten Beispiel.
Java-Code
String one = "abc";
String two = "abc";
Übrigens, wenn Sie einfach schreiben "ab" + "c", führt der Java-Compiler zur Kompilierungszeit eine Verkettung durch und der generierte Code ist genau der gleiche. Dies funktioniert nur, wenn alle Zeichenfolgen zur Kompilierungszeit bekannt sind.
Klasse konstanter Pool
Jede Klasse verfügt über einen eigenen Konstantenpool - eine Liste von Konstantenwerten, die wiederverwendet werden können, wenn sie im Quellcode mehrmals vorkommen. Es enthält allgemeine Zeichenfolgen, Zahlen, Methodennamen usw.
Hier sind die Inhalte des konstanten Pools in unserem obigen Beispiel.
const #2 = String #38;
const #38 = Asciz abc;
Das Wichtigste ist die Unterscheidung zwischen Stringkonstantem Objekt ( #2) und Unicode-codiertem Text "abc"( #38), auf den die Zeichenfolge zeigt.
Bytecode
Hier wird Bytecode generiert. Beachten Sie, dass sowohl oneals auch twoReferenzen mit derselben #2Konstante zugewiesen werden, die auf "abc"string zeigt:
ldc #2;
astore_1
ldc #2;
astore_2
Ausgabe
Für jedes Beispiel drucke ich die folgenden Werte:
System.out.println(showInternalCharArrayHashCode(one));
System.out.println(showInternalCharArrayHashCode(two));
System.out.println(System.identityHashCode(one));
System.out.println(System.identityHashCode(two));
Kein Wunder, dass beide Paare gleich sind:
23583040
23583040
8918249
8918249
Dies bedeutet, dass nicht nur beide Objekte auf dasselbe zeigen char[](derselbe Text darunter), sodass der equals()Test bestanden wird. Aber noch mehr oneund twosind genau die gleichen Referenzen! So one == twoist es auch. Offensichtlich wenn oneund twoauf dasselbe Objekt zeigen, dann one.valueund two.valuemuss gleich sein.
Wörtlich und new String()
Java-Code
Nun das Beispiel, auf das wir alle gewartet haben - ein String-Literal und ein neues Stringmit demselben Literal. Wie wird das funktionieren?
String one = "abc";
String two = new String("abc");
Die Tatsache, dass die "abc"Konstante im Quellcode zweimal verwendet wird, sollte Ihnen einen Hinweis geben ...
Klasse konstanter Pool
Das gleiche wie oben.
Bytecode
ldc #2;
astore_1
new #3;
dup
ldc #2;
invokespecial #4;
astore_2
Schau genau hin! Das erste Objekt wird auf die gleiche Weise wie oben erstellt, keine Überraschung. Es wird nur ein konstanter Verweis auf bereits erstellt String( #2) aus dem konstanten Pool verwendet. Das zweite Objekt wird jedoch über einen normalen Konstruktoraufruf erstellt. Aber! Der erste Stringwird als Argument übergeben. Dies kann dekompiliert werden zu:
String two = new String(one);
Ausgabe
Die Ausgabe ist etwas überraschend. Das zweite Paar, das Verweise auf StringObjekte darstellt, ist verständlich - wir haben zwei StringObjekte erstellt - eines wurde für uns im konstanten Pool erstellt und das zweite wurde manuell für erstellt two. Aber warum schlägt das erste Paar auf der Erde vor, dass beide StringObjekte auf dasselbe char[] valueArray zeigen?!
41771
41771
8388097
16585653
Wenn Sie sich die Funktionsweise des String(String)Konstruktors ansehen (hier stark vereinfacht), wird dies deutlich :
public String(String original) {
this.offset = original.offset;
this.count = original.count;
this.value = original.value;
}
Sehen? Wenn Sie ein neues StringObjekt basierend auf einem vorhandenen erstellen , wird es wiederverwendetchar[] value . Strings unveränderlich sind, muss keine Datenstruktur kopiert werden, von der bekannt ist, dass sie niemals geändert wird.
Ich denke, dies ist der Hinweis auf Ihr Problem: Selbst wenn Sie zwei StringObjekte haben, können diese dennoch auf denselben Inhalt verweisen. Und wie Sie sehen können, ist das StringObjekt selbst ziemlich klein.
Laufzeitänderung und intern()
Java-Code
Angenommen, Sie haben anfangs zwei verschiedene Zeichenfolgen verwendet, aber nach einigen Änderungen sind alle gleich:
String one = "abc";
String two = "?abc".substring(1);
Der Java-Compiler (zumindest meiner) ist nicht clever genug, um eine solche Operation zur Kompilierungszeit auszuführen.
Klasse konstanter Pool
Plötzlich hatten wir zwei konstante Zeichenfolgen, die auf zwei verschiedene konstante Texte zeigten:
const #2 = String #44;
const #3 = String #45;
const #44 = Asciz abc;
const #45 = Asciz ?abc;
Bytecode
ldc #2;
astore_1
ldc #3;
iconst_1
invokevirtual #4;
astore_2
Die Faustschnur ist wie gewohnt aufgebaut. Die zweite wird erstellt, indem zuerst die konstante "?abc"Zeichenfolge geladen und dann aufgerufen substring(1)wird.
Ausgabe
Kein Wunder hier - wir haben zwei verschiedene Zeichenfolgen, die auf zwei verschiedene char[]Texte im Speicher verweisen :
27379847
7615385
8388097
16585653
Nun, die Texte sind nicht wirklich unterschiedlich , die equals()Methode wird immer noch nachgeben true. Wir haben zwei unnötige Kopien desselben Textes.
Jetzt sollten wir zwei Übungen machen. Versuchen Sie zunächst:
two = two.intern();
vor dem Drucken von Hash-Codes. Nicht nur beide oneund twozeigen auf den gleichen Text, sondern sie sind die gleiche Referenz!
11108810
11108810
15184449
15184449
Das bedeutet , beide one.equals(two)und one == twoTests wird vorübergehen. Außerdem haben wir etwas Speicherplatz gespart, da "abc"Text nur einmal im Speicher angezeigt wird (die zweite Kopie wird durch Müll gesammelt).
Die zweite Übung ist etwas anders. Schauen Sie sich Folgendes an:
String one = "abc";
String two = "abc".substring(1);
Offensichtlich oneund twosind zwei verschiedene Objekte, die auf zwei verschiedene Texte verweisen. Aber wie kommt es, dass die Ausgabe darauf hindeutet, dass beide auf dasselbe char[]Array verweisen ?!?
23583040
23583040
11108810
8918249
Ich werde die Antwort dir überlassen. Sie erfahren, wie es substring()funktioniert, welche Vorteile ein solcher Ansatz bietet und wann er zu großen Problemen führen kann .