Ist String Literal Pool eine Sammlung von Verweisen auf das String-Objekt oder eine Sammlung von Objekten?

73

Ich bin alle verwirrt, nachdem ich den Artikel auf der Javaranch-Website von Corey McGlone, dem Autor von The SCJP Tip Line, gelesen habe. genannt Strings, Literally und der SCJP Java 6 Programmer Guide von Kathy Sierra (Mitbegründerin von javaranch) und Bert Bates.

Ich werde versuchen zu zitieren, was Herr Corey und Frau Kathy Sierra über String Literal Pool zitiert haben.

1. Laut Corey McGlone:

  • String Literal Pool ist eine Sammlung von Referenzen, die auf die String-Objekte verweisen.

  • String s = "Hello"; (Angenommen, auf dem Heap befindet sich kein Objekt mit dem Namen "Hallo"), erstellt ein String-Objekt "Hello"auf dem Heap und platziert einen Verweis auf dieses Objekt im String-Literal-Pool (Konstantentabelle).

  • String a = new String("Bye");(Angenommen, auf dem Heap befindet sich kein Objekt mit dem Namen "Bye". Der newOperator verpflichtet die JVM, ein Objekt auf dem Heap zu erstellen.

Jetzt ist die Erklärung des "new"Operators für die Erstellung eines Strings und seiner Referenz in diesem Artikel etwas verwirrend, daher setze ich den Code und die Erklärung aus dem Artikel selbst so ein, wie er unten ist.

public class ImmutableStrings
{
    public static void main(String[] args)
    {
        String one = "someString";
        String two = new String("someString");

        System.out.println(one.equals(two));
        System.out.println(one == two);
    }
}

In diesem Fall haben wir aufgrund des Schlüsselworts tatsächlich ein etwas anderes Verhalten. "new." In einem solchen Fall werden Verweise auf die beiden String-Literale immer noch in die Konstantentabelle (den String-Literal-Pool) eingefügt, aber wenn Sie zum Schlüsselwort kommen "new,"Die JVM muss zur Laufzeit ein neues String-Objekt erstellen, anstatt das aus der Konstantentabelle zu verwenden.

Hier ist das Diagramm, das es erklärt.

Geben Sie hier die Bildbeschreibung ein

Bedeutet das also, dass auch der String Literal Pool einen Verweis auf dieses Objekt hat?

Hier ist der Link zum Artikel von Corey McGlone

http://www.javaranch.com/journal/200409/Journal200409.jsp#a1

2. Laut Kathy Sierra und Bert Bates im SCJP-Buch:

  • Um Java speichereffizienter zu machen, hat die JVM einen speziellen Speicherbereich namens "String-Konstantenpool" reserviert. Wenn der Compiler auf ein String-Literal stößt, überprüft er den Pool, um festzustellen, ob bereits ein identischer String vorhanden ist oder nicht. Wenn nicht, wird ein neues String-Literal-Objekt erstellt.

  • String s = "abc"; // Erstellt ein String-Objekt und eine Referenzvariable ....

    das ist in Ordnung, aber dann war ich durch diese Aussage verwirrt:

  • String s = new String("abc") // Erstellt zwei Objekte und eine Referenzvariable.

    In dem Buch heißt es, dass ... ein neues String-Objekt im normalen Speicher (ohne Pool) und "s" darauf verweisen ... während ein zusätzliches Literal "abc" in den Pool gestellt wird.

    Die obigen Zeilen im Buch kollidieren mit denen im Artikel von Corey McGlone.

    • Wenn der String-Literal-Pool eine Sammlung von Verweisen auf das von Corey McGlone erwähnte String-Objekt ist, warum wird dann das Literal-Objekt "abc" in den Pool gestellt (wie im Buch erwähnt)?

    • Und wo befindet sich dieser String Literal Pool?

Bitte klären Sie diesen Zweifel, obwohl es beim Schreiben eines Codes nicht allzu wichtig ist, aber unter dem Gesichtspunkt der Speicherverwaltung sehr wichtig ist, und das ist der Grund, warum ich diese Funda löschen möchte.

Kumar Vivek Mitra
quelle
1
Wie der Pool verwaltet wird, hängt möglicherweise bis zu einem gewissen Grad von der JVM-Implementierung ab. Solange etwas nicht durch die Sprachspezifikation festgelegt ist , können sie experimentieren. Ob der Pool Referenzen oder Objekte enthält, hängt meiner Meinung nach davon ab.
MvG
Ich bin kürzlich auf genau dieselbe Frage mit denselben 2 Ressourcen gestoßen, die Sie erwähnt haben. Ich denke, durch Anweisung wird das Objekt "abc" im Buch in den Pool gestellt. Der Autor bedeutet, dass der Verweis auf das Objekt "abc" im Pool gespeichert wird. Richtig? Die akzeptierte Antwort ist ziemlich informativ, aber ich denke, das wird gefragt.
mustafa1993

Antworten:

111

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;    //  abc
//...
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; //String abc
astore_1    //one
ldc #2; //String abc
astore_2    //two

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; //String abc
astore_1    //one

new #3; //class java/lang/String
dup
ldc #2; //String abc
invokespecial   #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
astore_2    //two

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);  //also two = "abc"

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;    //  abc
const #3 = String   #45;    //  ?abc
const #44 = Asciz   abc;
const #45 = Asciz   ?abc;

Bytecode

ldc #2; //String abc
astore_1    //one

ldc #3; //String ?abc
iconst_1
invokevirtual   #4; //Method String.substring:(I)Ljava/lang/String;
astore_2    //two

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 .

Tomasz Nurkiewicz
quelle
2
+1, danke für die gründlichen Kenntnisse von String. Aber die oben dargestellten Informationen sind etwas, über das ich bereits eine Idee hatte. Meine Frage bleibt bestehen. Ist der String-Literal-Pool eine Sammlung von Objekten oder Referenzen? Und wenn der String-Literal-Pool ein Objekt enthält, wird auch ein neuer Operator verwendet erstellt zwei String-Objekte, eines außerhalb und eines innerhalb des Speicherpools, und die Referenz zeigt auf das außerhalb des Speicherpools.
Kumar Vivek Mitra
1
+1 brillante Antwort. Aber ich denke, in der Ausgabe des letzten Beispiels erhalten Sie einen ähnlichen HashCode für das String-Objekt, nicht für das vlaue char-Array.
Eng.Fouad
6
Ist String Literal Pool eine Sammlung von Verweisen auf das String-Objekt oder eine Sammlung von Objekten , aber wie lautet die genaue Antwort?
smileVann
3
Ab Java 7 erstellt der Teilstring eine neue Kopie des erforderlichen Teils des Arrays und verweist nicht auf denselben.
Heisenberg
4
Diese Antwort gibt keine Antwort auf die ursprüngliche Frage. Wie sieht String Pool intern aus? Ist es eine Sammlung von Referenzen oder eine Sammlung von Zeichenfolgen?
Eugene Maysyuk