Was ist Java String Interning?

234

Was ist String Interning in Java, wann sollte ich es verwenden und warum ?

saplingPro
quelle
2
wenn String a = new String("abc"); String b = new String("abc"); danna.intern() == b.intern()
Asanka Siriwardena
Beispiel für die Internierung der Checkout- Zeichenfolge
Ronak Poriya
Kommt darauf String.intern()an ClassLoader, was bedeutet, dass verschiedene Klassenlader "verschiedene" erstellen und unterschiedliche " Stringverursachen" intern?
AlikElzin-Kilaka
1
@ AlikElzin-kilaka nein, Klassenlader sind für das String-Interning völlig irrelevant. Wenn Sie das nächste Mal eine Frage haben, öffnen Sie bitte eine neue Frage, anstatt sie als Kommentar zu einer anderen Frage zu veröffentlichen.
Holger

Antworten:

233

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern ()

Wenn Sie String.intern () für eine Reihe von Zeichenfolgen ausführen, wird sichergestellt, dass alle Zeichenfolgen mit demselben Inhalt denselben Speicher verwenden. Wenn Sie also eine Liste mit Namen haben, in denen 'John' 1000 Mal vorkommt, stellen Sie durch Internierung sicher, dass nur einem 'John' tatsächlich Speicher zugewiesen wird.

Dies kann nützlich sein, um den Speicherbedarf Ihres Programms zu reduzieren. Beachten Sie jedoch, dass der Cache von JVM in einem permanenten Speicherpool verwaltet wird, dessen Größe im Vergleich zum Heap normalerweise begrenzt ist. Verwenden Sie daher intern nicht, wenn Sie nicht zu viele doppelte Werte haben.


Weitere Informationen zu Speicherbeschränkungen bei der Verwendung von intern ()

Einerseits können Sie String-Duplikate entfernen, indem Sie sie internalisieren. Das Problem besteht darin, dass die internalisierten Zeichenfolgen an die permanente Generation gesendet werden. Dies ist ein Bereich der JVM, der für Nichtbenutzerobjekte wie Klassen, Methoden und andere interne JVM-Objekte reserviert ist. Die Größe dieses Bereichs ist begrenzt und normalerweise viel kleiner als der Haufen. Wenn Sie intern () für einen String aufrufen, wird dieser vom Heap in die permanente Generierung verschoben, und es besteht die Gefahr, dass Ihnen der PermGen-Speicherplatz ausgeht.

- Von: http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html


Ab JDK 7 (ich meine in HotSpot) hat sich etwas geändert.

In JDK 7 werden internierte Zeichenfolgen nicht mehr in der permanenten Generierung des Java-Heaps zugewiesen, sondern im Hauptteil des Java-Heaps (als junge und alte Generation bezeichnet) zusammen mit den anderen von der Anwendung erstellten Objekten . Diese Änderung führt dazu, dass sich mehr Daten im Haupt-Java-Heap und weniger Daten in der permanenten Generierung befinden. Daher müssen möglicherweise die Heap-Größen angepasst werden. Die meisten Anwendungen sehen aufgrund dieser Änderung nur relativ kleine Unterschiede in der Heap-Nutzung, aber größere Anwendungen, die viele Klassen laden oder die String.intern () -Methode stark nutzen, werden größere Unterschiede feststellen.

- Von Java SE 7 Funktionen und Verbesserungen

Update: Internierte Zeichenfolgen werden ab Java 7 im Hauptheap gespeichert. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes

Ashwinee K Jha
quelle
1
"Beachten Sie jedoch, dass der Cache von JVM in einem permanenten Speicherpool verwaltet wird, dessen Größe normalerweise begrenzt ist." Können Sie dies erklären? Ich habe es nicht verstanden
saplingPro
2
Die "internierten" Zeichenfolgen werden in einem speziellen Speicherbereich in der JVM gespeichert. Dieser Speicherbereich hat normalerweise eine feste Größe und ist nicht Teil des regulären Java-Heaps, in dem andere Daten gespeichert sind. Aufgrund der festen Größe kann es vorkommen, dass dieser permanente Speicherbereich mit all Ihren Zeichenfolgen gefüllt wird, was zu hässlichen Problemen führt (Klassen können nicht geladen werden und andere Dinge).
Cello
@cello also, ist es ähnlich wie Caching?
SchösslingPro
8
@grassPro: Ja, es ist eine Art Caching, das von der JVM nativ bereitgestellt wird. Aufgrund der Zusammenführung von Sun / Oracle JVM und JRockit versuchen die JVM-Ingenieure, den permanenten Speicherbereich in JDK 8 ( openjdk.java.net/jeps/122 ) zu entfernen jede Größenbeschränkung in der Zukunft.
Cello
9
Programmierer sollten sich auch darüber im Klaren sein, dass das Internieren von Zeichenfolgen Auswirkungen auf die Sicherheit haben kann. Wenn Sie vertraulichen Text wie Kennwörter als Zeichenfolgen im Speicher haben, bleibt dieser möglicherweise sehr lange im Speicher, selbst wenn die tatsächlichen Zeichenfolgenobjekte seit langem GC-fähig sind. Das kann problematisch sein, wenn Bösewichte irgendwie Zugriff auf einen Speicherauszug erhalten. Dieses Problem besteht auch ohne Internierung (da GC zunächst nicht deterministisch ist usw.), macht es jedoch etwas schlimmer. Es ist immer eine gute Idee, char[]anstelle von Stringsensiblem Text zu verwenden und ihn auf Null zu setzen, sobald er nicht mehr benötigt wird.
Chris
71

Es gibt einige "eingängige Interview" -Fragen, z. B. warum Sie gleichberechtigt sind! wenn Sie den folgenden Code ausführen.

String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");

Wenn Sie Strings vergleichen möchten, sollten Sie verwenden equals(). Das obige wird gleich gedruckt, da das vom Compiler testStringbereits für Sie interniert wurde . Sie können die Zeichenfolgen mithilfe der internen Methode selbst internieren, wie in den vorherigen Antworten gezeigt.

Maslan
quelle
5
Ihr Beispiel ist schwierig, da es auch dann zu demselben Druck führt, wenn Sie die equalsMethode verwenden. Möglicherweise möchten Sie einen new String()Vergleich hinzufügen , um die Unterscheidung deutlicher darzustellen.
Giannis Christofakis
@giannischristofakis aber wenn wir neuen String () verwenden, würde der == nicht fehlschlagen? Verinnerlicht Java automatisch auch neue Zeichenfolgen?
Deepak Selvakumar
@giannischristofakis natürlich, wenn Sie neue String () verwenden, wird es auf == fehlschlagen. aber new String (...). intern () schlägt bei == nicht fehl, da intern denselben String zurückgibt. Einfach angenommen, der Compiler macht einen neuen String (). Intern in Literalen
maslan
42

JLS

JLS 7 3.10.5 definiert es und gibt ein praktisches Beispiel:

Darüber hinaus bezieht sich ein String-Literal immer auf dieselbe Instanz der Klasse String. Dies liegt daran, dass Zeichenfolgenliterale - oder allgemeiner Zeichenfolgen, die die Werte konstanter Ausdrücke sind (§15.28) - mit der Methode String.intern "interniert" werden, um eindeutige Instanzen gemeinsam zu nutzen.

Beispiel 3.10.5-1. String-Literale

Das Programm bestehend aus der Zusammenstellungseinheit (§7.3):

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

und die Zusammenstellungseinheit:

package other;
public class Other { public static String hello = "Hello"; }

erzeugt die Ausgabe:

true true true true false true

JVMS

Laut JVMS 7 5.1 wird das Internieren mit einer dedizierten CONSTANT_String_infoStruktur magisch und effizient implementiert (im Gegensatz zu den meisten anderen Objekten mit allgemeineren Darstellungen):

Ein String-Literal ist eine Referenz auf eine Instanz der Klasse String und wird aus einer CONSTANT_String_info-Struktur (§4.4.3) in der binären Darstellung einer Klasse oder Schnittstelle abgeleitet. Die Struktur CONSTANT_String_info gibt die Folge von Unicode-Codepunkten an, die das Zeichenfolgenliteral bilden.

Die Java-Programmiersprache erfordert, dass identische Zeichenfolgenliterale (dh Literale, die dieselbe Folge von Codepunkten enthalten) auf dieselbe Instanz der Klasse String verweisen müssen (JLS §3.10.5). Wenn die Methode String.intern für eine beliebige Zeichenfolge aufgerufen wird, ist das Ergebnis außerdem ein Verweis auf dieselbe Klasseninstanz, die zurückgegeben würde, wenn diese Zeichenfolge als Literal angezeigt würde. Daher muss der folgende Ausdruck den Wert true haben:

("a" + "b" + "c").intern() == "abc"

Um ein Zeichenfolgenliteral abzuleiten, untersucht die Java Virtual Machine die Reihenfolge der Codepunkte, die in der Struktur CONSTANT_String_info angegeben sind.

  • Wenn die Methode String.intern zuvor für eine Instanz der Klasse String aufgerufen wurde, die eine Folge von Unicode-Codepunkten enthält, die mit der in der Struktur CONSTANT_String_info angegebenen identisch sind, ist das Ergebnis der Ableitung des String-Literal ein Verweis auf dieselbe Instanz der Klasse String.

  • Andernfalls wird eine neue Instanz der Klasse String erstellt, die die von der Struktur CONSTANT_String_info angegebene Folge von Unicode-Codepunkten enthält. Ein Verweis auf diese Klasseninstanz ist das Ergebnis der Ableitung von Zeichenfolgenliteralen. Schließlich wird die interne Methode der neuen String-Instanz aufgerufen.

Bytecode

Lassen Sie uns einen OpenJDK 7-Bytecode dekompilieren, um die Internierung in Aktion zu sehen.

Wenn wir dekompilieren:

public class StringPool {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a);
        System.out.println(b);
        System.out.println(a == c);
    }
}

Wir haben auf dem ständigen Pool:

#2 = String             #32   // abc
[...]
#32 = Utf8               abc

und main:

 0: ldc           #2          // String abc
 2: astore_1
 3: ldc           #2          // String abc
 5: astore_2
 6: new           #3          // class java/lang/String
 9: dup
10: ldc           #2          // String abc
12: invokespecial #4          // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6          // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic     #5          // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne     42
38: iconst_1
39: goto          43
42: iconst_0
43: invokevirtual #7          // Method java/io/PrintStream.println:(Z)V

Beachten Sie, wie:

  • 0und 3: die gleiche ldc #2Konstante wird geladen (die Literale)
  • 12: Eine neue String-Instanz wird erstellt (mit #2als Argument)
  • 35: aund cwerden als reguläre Objekte mit verglichenif_acmpne

Die Darstellung konstanter Zeichenfolgen ist auf dem Bytecode ziemlich magisch:

  • es verfügt über eine eigene CONSTANT_String_info Struktur, anders als bei normalen Objekten (zB new String)
  • Die Struktur zeigt auf eine CONSTANT_Utf8_info-Struktur , die die Daten enthält. Dies sind die einzigen erforderlichen Daten, um die Zeichenfolge darzustellen.

und das obige JVMS-Zitat scheint zu sagen, dass immer dann, wenn der Utf8, auf den gezeigt wird, derselbe ist, identische Instanzen von geladen werden ldc.

Ich habe ähnliche Tests für Felder durchgeführt und:

Schlussfolgerung : Es gibt eine direkte Bytecode-Unterstützung für den Zeichenfolgenpool und die Speicherdarstellung ist effizient.

Bonus: Vergleichen Sie das mit dem Integer-Pool , der keine direkte Bytecode-Unterstützung bietet (dh kein CONSTANT_String_infoAnalogon).

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
19

Update für Java 8 oder höher . In Java 8 wird der PermGen-Speicherplatz (Permanent Generation) entfernt und durch den Meta-Speicherplatz ersetzt. Der String-Pool-Speicher wird auf den Heap von JVM verschoben.

Im Vergleich zu Java 7 wird die Größe des String-Pools im Heap erhöht. Daher haben Sie mehr Platz für verinnerlichte Zeichenfolgen, aber weniger Speicher für die gesamte Anwendung.

Eine weitere Sache, Sie haben bereits gewusst, dass beim Vergleichen von 2 (Verweisen von) Objekten in Java ' ==' zum Vergleichen der Objektreferenz ' equals' zum Vergleichen des Objektinhalts verwendet wird.

Überprüfen wir diesen Code:

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

Ergebnis:

value1 == value2 ---> wahr

value1 == value3 ---> falsch

value1.equals(value3) ---> wahr

value1 == value3.intern() ---> wahr

Deshalb sollten Sie ' equals' verwenden, um 2 String-Objekte zu vergleichen. Und so ist intern()es nützlich.

nguyentt
quelle
2

String Interning ist eine Optimierungstechnik des Compilers. Wenn Sie zwei identische Zeichenfolgenliterale in einer Kompilierungseinheit haben, stellt der generierte Code sicher, dass nur ein Zeichenfolgenobjekt für die gesamte Instanz dieses Literals (in doppelte Anführungszeichen eingeschlossene Zeichen) in der Assembly erstellt wird.

Ich komme aus dem C # -Hintergrund, daher kann ich dies anhand eines Beispiels erklären:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

Ausgabe der folgenden Vergleiche:

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

Anmerkung 1 : Objekte werden als Referenz verglichen.

Anmerkung 2 : typeof (int) .Name wird durch die Reflektionsmethode ausgewertet, sodass es beim Kompilieren nicht ausgewertet wird. Hier werden diese Vergleiche zur Kompilierungszeit durchgeführt.

Analyse der Ergebnisse: 1) true, da beide dasselbe Literal enthalten und der generierte Code daher nur ein Objekt enthält, das auf "Int32" verweist. Siehe Anmerkung 1 .

2) wahr, weil der Inhalt beider Werte überprüft wird, was gleich ist.

3) FALSE, weil str2 und obj nicht dasselbe Literal haben. Siehe Anmerkung 2 .

Robin Gupta
quelle
3
Es ist stärker als das. Jedes String-Literal, das von demselben Klassenladeprogramm geladen wird, verweist auf denselben String. Siehe die JLS- und JVM-Spezifikation.
Marquis von Lorne
1
@ user207421 in der Tat ist es sogar irrelevant, zu welchem ​​Klassenlader das String-Literal gehört.
Holger
1
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references

for eg: String s1=new String("abc");
        String s2="abc";
        String s3="abc";

s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true

now if we do intern on s1
s1=s1.intern() 

//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.
Rohan Kshirsagar
quelle
0

Aus dem Buch des OCP Java SE 11-Programmierers Deshmukh fand ich die einfachste Erklärung für Interning, die wie folgt lautete: Da Zeichenfolgen Objekte sind und alle Objekte in Java immer nur im Heap-Bereich gespeichert sind, werden alle Zeichenfolgen im Heap-Bereich gespeichert. Java behält jedoch Zeichenfolgen bei, die ohne Verwendung des neuen Schlüsselworts in einem speziellen Bereich des Heap-Bereichs erstellt wurden, der als "Zeichenfolgenpool" bezeichnet wird. Java behält die mit dem neuen Schlüsselwort erstellten Zeichenfolgen im regulären Heap-Bereich bei.

Der Zweck des Zeichenfolgenpools besteht darin, eine Reihe eindeutiger Zeichenfolgen zu verwalten. Jedes Mal, wenn Sie eine neue Zeichenfolge erstellen, ohne das neue Schlüsselwort zu verwenden, prüft Java, ob dieselbe Zeichenfolge bereits im Zeichenfolgenpool vorhanden ist. In diesem Fall gibt Java einen Verweis auf dasselbe String-Objekt zurück. Wenn dies nicht der Fall ist, erstellt Java ein neues String-Objekt im String-Pool und gibt seinen Verweis zurück. Wenn Sie beispielsweise die Zeichenfolge "Hallo" zweimal in Ihrem Code verwenden, wie unten gezeigt, erhalten Sie einen Verweis auf dieselbe Zeichenfolge. Wir können diese Theorie tatsächlich testen, indem wir zwei verschiedene Referenzvariablen mit dem Operator == vergleichen , wie im folgenden Code gezeigt:

String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true

String str3 = new String("hello");
String str4 = new String("hello");

System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false 

Der Operator == prüft einfach, ob zwei Referenzen auf dasselbe Objekt verweisen oder nicht, und gibt in diesem Fall true zurück. Im obigen Code erhält str2 den Verweis auf dasselbe String-Objekt, das zuvor erstellt wurde. Allerdings str3 und str4 get Verweise auf zwei völlig verschiedene String - Objekte. Deshalb gibt str1 == str2 true zurück, aber str1 == str3 und str3 == str4 geben false zurück. In der Tat, wenn Sie einen neuen String machen ("Hallo"); Es werden zwei Zeichenfolgenobjekte anstelle von nur einem erstellt, wenn dies das erste Mal ist, dass die Zeichenfolge "Hallo" in einem beliebigen Programm verwendet wird - eines im Zeichenfolgenpool aufgrund der Verwendung einer Zeichenfolge in Anführungszeichen und eines im regulären Heap-Bereich, weil der Verwendung eines neuen Schlüsselworts.

String-Pooling ist Javas Methode, um Programmspeicher zu sparen, indem die Erstellung mehrerer String-Objekte mit demselben Wert vermieden wird. Es ist möglich, eine Zeichenfolge aus dem Zeichenfolgenpool für eine Zeichenfolge abzurufen, die mit dem neuen Schlüsselwort mithilfe der internen Methode von String erstellt wurde. Es wird als "Internierung" von Zeichenfolgenobjekten bezeichnet. Beispielsweise,

String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj

System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
Hamza
quelle