Ist es eine gute Praxis, java.lang.String.intern () zu verwenden?

194

Der Javadoc über String.intern()gibt nicht viele Details. (Kurz gesagt: Es wird eine kanonische Darstellung der Zeichenfolge zurückgegeben, sodass internierte Zeichenfolgen mit verglichen werden können. ==)

  • Wann würde ich diese Funktion zugunsten von nutzen String.equals()?
  • Gibt es Nebenwirkungen, die im Javadoc nicht erwähnt werden, dh mehr oder weniger Optimierung durch den JIT-Compiler?
  • Gibt es weitere Verwendungen von String.intern()?
Daniel Rikowski
quelle
14
Das Aufrufen von intern () hat seine eigenen Auswirkungen auf die Leistung. Die Verwendung von intern () zur Verbesserung der Leistung muss getestet werden, um sicherzustellen, dass Ihr Programm wirklich erheblich beschleunigt wird, um die zusätzliche Komplexität wert zu sein. Sie können dies auch verwenden, um den Speicherverbrauch für große Tabellen mit zuverlässig wiederholenden Werten zu reduzieren. In beiden Fällen gibt es jedoch andere Optionen, die möglicherweise besser sind.
Peter Lawrey
Ja, intern () hat seine eigenen Auswirkungen auf die Leistung. Vor allem, weil die Kosten für intern () linear steigen, wenn Sie Zeichenfolgen internieren und einen Verweis darauf behalten. Zumindest auf einer Sonne / Orakel 1.6.0_30 vm.
Lacroix1547

Antworten:

125

Wann würde ich diese Funktion zugunsten von String.equals () verwenden?

wenn Sie Geschwindigkeit benötigen, da Sie Zeichenfolgen anhand der Referenz vergleichen können (== ist schneller als gleich)

Gibt es Nebenwirkungen, die im Javadoc nicht erwähnt werden?

Der Hauptnachteil ist, dass Sie daran denken müssen, sicherzustellen, dass Sie tatsächlich alle Zeichenfolgen intern () ausführen, die Sie vergleichen möchten. Es ist leicht zu vergessen, alle Zeichenfolgen zu internieren (), und dann können Sie verwirrend falsche Ergebnisse erhalten. Stellen Sie außerdem für alle sicher, dass Sie sehr deutlich dokumentieren, dass Sie sich auf die zu verinnerlichenden Zeichenfolgen verlassen.

Der zweite Nachteil bei der Internalisierung von Zeichenfolgen besteht darin, dass die intern () -Methode relativ teuer ist. Es muss den Pool eindeutiger Zeichenfolgen verwalten, damit es ein gutes Stück Arbeit erledigt (auch wenn die Zeichenfolge bereits verinnerlicht wurde). Seien Sie also bei Ihrem Code-Design vorsichtig, damit Sie z. B. alle geeigneten Zeichenfolgen bei der Eingabe intern () verwenden, damit Sie sich nicht mehr darum kümmern müssen.

(von JGuru)

Dritter Nachteil (nur Java 7 oder weniger): Internierte Strings leben im PermGen-Raum, der normalerweise recht klein ist. Möglicherweise stoßen Sie auf einen OutOfMemoryError mit viel freiem Heap-Speicherplatz.

(von Michael Borgwardt)

dfa
quelle
64
Ein dritter Nachteil: Internierte Strings leben im PermGen-Raum, der normalerweise recht klein ist. Möglicherweise stoßen Sie auf einen OutOfMemoryError mit viel freiem Heap-Speicherplatz.
Michael Borgwardt
15
AFAIK neuere VMs sammeln auch den PermGen-Speicherplatz.
Daniel Rikowski
31
Bei Intern geht es um Speicherverwaltung, nicht um Vergleichsgeschwindigkeit. Der Unterschied zwischen if (s1.equals(s2))und if (i1 == i2)ist minimal, es sei denn, Sie haben viele lange Zeichenfolgen mit denselben Hauptfiguren. In den meisten realen Anwendungen (außer URLs) unterscheiden sich die Zeichenfolgen innerhalb der ersten Zeichen. Und lange If-else-Ketten riechen sowieso nach Code: Verwenden Sie Aufzählungen und Funktionskarten.
kdgregory
25
Sie können weiterhin die Syntax s1.equals in Ihrem gesamten Programm verwenden. Verwenden Sie NICHT ==, .equals verwenden == intern, um die Auswertung kurzzuschließen
gtrak
15
Michael Borgwardt hat NICHT gesagt, dass internierte Strings nicht durch Müll gesammelt werden können. Und das ist eine falsche Behauptung. Was Michaels Kommentare (richtig) sagen, ist subtiler.
Stephen C.
193

Dies hat (fast) nichts mit einem String-Vergleich zu tun. Das Internieren von Zeichenfolgen dient zum Speichern von Speicher, wenn Ihre Anwendung viele Zeichenfolgen mit demselben Inhalt enthält. Wenn Sie String.intern()die Anwendung verwenden, wird dies auf lange Sicht nur eine Instanz haben. Ein Nebeneffekt ist, dass Sie einen schnellen Referenzgleichheitsvergleich anstelle eines normalen Zeichenfolgenvergleichs durchführen können (dies ist jedoch normalerweise nicht ratsam, da es wirklich leicht zu brechen ist, wenn Sie nur den Praktikanten vergessen eine einzelne Instanz).

Daniel Brückner
quelle
4
Das ist nicht richtig. Das Internieren von Zeichenfolgen erfolgt immer automatisch, wenn jeder Zeichenfolgenausdruck ausgewertet wird. Es gibt immer eine Kopie für jede eindeutige Zeichenfolge und sie wird "intern freigegeben", wenn mehrere Verwendungen auftreten. Das Aufrufen von String.intern () macht dies nicht alles möglich - es gibt nur die interne kanonische Darstellung zurück. Siehe Javadoc.
Glen Best
16
Klarstellung - Internierung erfolgt immer automatisch für Konstanten zur Kompilierungszeit (Literale & feste Ausdrücke). Außerdem tritt es auf, wenn String.intern () für dynamisch ausgewertete Strings zur Laufzeit aufgerufen wird.
Glen Best
Du meinst also, wenn es 1000 Objekte von "Hello" in Heap gibt und ich intern () für eines von ihnen durchführe, werden dann 999 verbleibende Objekte automatisch zerstört?
Arun Raaj
@ArunRaaj nein, werden Sie Ihre 1000 haben nach wie vor auf dem Heap, und ein extra ein im internen Pool, die von später zur erneuten Verwendung bereit sein kann , str.intern()wenn strist "Hello".
Matthieu
37

String.intern()ist definitiv Müll, der in modernen JVMs gesammelt wird.
Aufgrund der GC-Aktivität geht NIEMALS der Speicherplatz aus:

// java -cp . -Xmx128m UserOfIntern

public class UserOfIntern {
    public static void main(String[] args) {
        Random random = new Random();
        System.out.println(random.nextLong());
        while (true) {
            String s = String.valueOf(random.nextLong());
            s = s.intern();
        }
    }
}

Sehen Sie mehr (von mir) über den Mythos von nicht GCed String.intern () .

Gili Nachum
quelle
26
OutOfMemoryException- Nein, nicht der obige Code in meinem Gehirn : Link zum Javaturning-Artikel, der auf diesen Artikel verweist, der auf den Javaturning-Artikel verweist, der ... :-)
user85421
Obwohl Sie sehen können, dass der Beitrag bearbeitet wurde, um diesen Link hinzuzufügen;)
Riking
3
Möglicherweise möchten Sie erwähnen, dass Sie auch der Autor der externen Referenz sind, auf die Sie verlinken.
Thorbjørn Ravn Andersen
11
@ Carlos, der eine externe Referenz verknüpft, die zurück zum Stapelüberlauf verweist, sollte einen .. Stapelüberlauf verursachen :)
Seiti
2
@ Seiti Rundschreiben sind heutzutage leicht zu erkennen: p
Ajay
16

Ich habe kürzlich einen Artikel über die Implementierung von String.intern () in Java 6, 7 und 8 geschrieben: String.intern in Java 6, 7 und 8 - String Pooling .

Ich hoffe, es sollte genügend Informationen über die aktuelle Situation mit String-Pooling in Java enthalten.

In einer Nussschale:

  • Vermeiden Sie String.intern()in Java 6, da es in PermGen geht
  • Bevorzugen Sie String.intern()in Java 7 und Java 8: Es verbraucht 4-5x weniger Speicher als das Rollen Ihres eigenen Objektpools
  • Stellen Sie sicher, dass Sie abstimmen -XX:StringTableSize(die Standardeinstellung ist wahrscheinlich zu klein; stellen Sie eine Primzahl ein).
mik1
quelle
2
Bitte poste nicht nur Links zu deinem Blog, dies wird von manchen als SPAM angesehen. Plus Blog-Links haben eine bemerkenswerte Tendenz, einen 404-Tod zu sterben. Bitte fassen Sie Ihren Artikel entweder hier inline zusammen oder hinterlassen Sie diesen Link in einem Kommentar zur Frage.
Mat
3
Danke, dass du das @ mik1 geschrieben hast! Sehr informativer, klarer und aktueller Artikel. (Ich bin hierher zurückgekommen, um selbst einen Link dazu zu posten.)
Luke Usherwood
1
Vielen Dank für die Erwähnung des Arguments -XX. Sie können dies auch verwenden, um die Tabellenstatistiken anzuzeigen: -XX: + PrintStringTableStatistics
csadler
13

Der Vergleich von Strings mit == ist viel schneller als mit equals ()

5 Zeit schneller, aber da der String-Vergleich normalerweise nur einen kleinen Prozentsatz der gesamten Ausführungszeit einer Anwendung ausmacht, ist der Gesamtgewinn viel geringer und der endgültige Gewinn wird auf einige Prozent verwässert.

String.intern () zieht den String vom Heap weg und legt ihn in PermGen ab

Verinnerlichte Zeichenfolgen werden in einem anderen Speicherbereich abgelegt: Permanente Generierung. Dies ist ein Bereich der JVM, der für Nichtbenutzerobjekte wie Klassen, Methoden und andere interne JVM-Objekte reserviert ist. Die Größe dieses Gebiets ist begrenzt und das ist viel wertvoller als Haufen. Da dieser Bereich kleiner als Heap ist, besteht eine höhere Wahrscheinlichkeit, dass der gesamte Speicherplatz genutzt wird und eine OutOfMemoryException ausgelöst wird.

String.intern () String sind Müll gesammelt

In den neuen Versionen von JVM werden auch internalisierte Zeichenfolgen durch Müll gesammelt, wenn sie von keinem Objekt referenziert werden.

Wenn Sie die obigen 3 Punkte berücksichtigen, können Sie ableiten, dass String intern () nur in wenigen Situationen nützlich sein kann, wenn Sie viele Zeichenfolgenvergleiche durchführen. Es ist jedoch besser, keine interne Zeichenfolge zu verwenden, wenn Sie nicht genau wissen, was Sie sind sind dabei ...

Aleroot
quelle
1
Nur zum Hinzufügen können Heap-Speicherausnahmen manchmal wiederhergestellt werden, insbesondere in Thread-Modellen wie Webanwendungen. Wenn das Permgen erschöpft ist, ist eine Anwendung in der Regel permanent nicht funktionsfähig und führt häufig zu einem Ressourcen-Thrash, bis sie getötet wird.
Taylor
7

Wann würde ich diese Funktion zugunsten von String.equals () verwenden?

Angesichts der Tatsache, dass sie verschiedene Dinge tun, wahrscheinlich nie.

Das Internieren von Zeichenfolgen aus Leistungsgründen, damit Sie sie auf Referenzgleichheit vergleichen können, ist nur dann von Vorteil, wenn Sie eine Weile auf die Zeichenfolgen verweisen - Zeichenfolgen, die aus Benutzereingaben oder E / A stammen, werden nicht interniert.

Das bedeutet, dass Sie in Ihrer Anwendung Eingaben von einer externen Quelle erhalten und zu einem Objekt verarbeiten, das einen semantischen Wert hat - beispielsweise einen Bezeichner -, aber dieses Objekt hat einen Typ, der nicht von den Rohdaten zu unterscheiden ist, und unterschiedliche Regeln, wie der Programmierer vorgehen soll benutze es.

Es ist fast immer besser, einen UserIdTyp zu erstellen, der interniert ist (es ist einfach, einen thread-sicheren generischen Internierungsmechanismus zu erstellen) und sich wie eine offene Aufzählung verhält, als den java.lang.StringTyp mit Referenzsemantik zu überladen, wenn es sich zufällig um eine Benutzer-ID handelt.

Auf diese Weise erhalten Sie keine Verwirrung darüber, ob ein bestimmter String interniert wurde oder nicht, und Sie können jedes zusätzliche Verhalten, das Sie benötigen, in die offene Aufzählung einkapseln.

Pete Kirkham
quelle
6

Ich bin mir keiner Vorteile bewusst, und wenn es welche gäbe, würde man denken, dass equals () selbst intern () intern verwenden würde (was es nicht tut).

Busting intern () Mythen

Objekte
quelle
7
Obwohl Sie sagen, dass Sie sich keiner Vorteile bewusst sind, identifiziert Ihr geposteter Link den Vergleich über == als 5x schneller und daher wichtig für textzentrierten performanten Code
Brian Agnew
3
Wenn Sie viel Text vergleichen müssen, wird Ihnen schließlich der PermGen-Speicherplatz ausgehen. Wenn nicht so viel Text verglichen wird, spielt der Geschwindigkeitsunterschied keine Rolle. In beiden Fällen sollten Sie Ihre Zeichenfolgen nicht internieren (). Es lohnt sich nicht.
Bombe
Es wird auch weiter gesagt, dass der relative Gesamtgewinn in der Regel gering sein wird.
Objekte
Ich denke nicht, dass diese Art von Logik gültig ist. Guter Link!
Daniel Rikowski
1
@ DR: Welche Logik? Das ist ein großer Irrtum. @objects: Entschuldigung, aber Ihre Argumente sind nicht ausreichend begründet. Es gibt sehr gute Gründe für die Verwendung internund sehr gute Gründe, equalsdie dies standardmäßig nicht tun. Der Link, den Sie gepostet haben, ist ein kompletter Blödsinn. Der letzte Absatz gibt sogar zu, dass internes ein gültiges Verwendungsszenario gibt: schwere Textverarbeitung (z. B. ein Parser). Die Schlussfolgerung, dass „[XYZ] gefährlich ist, wenn Sie nicht wissen, was Sie tun“, ist so banal, dass es körperlich weh tut.
Konrad Rudolph
4

Daniel Brückner hat absolut recht. String-Internierung soll Speicher (Heap) sparen. Unser System verfügt derzeit über eine riesige Hashmap zum Speichern bestimmter Daten. Wenn das System skaliert, ist die Hashmap groß genug, um den Heap aus dem Speicher zu machen (wie wir getestet haben). Durch das Internieren aller duplizierten Zeichenfolgen und aller Objekte in der Hashmap sparen wir eine erhebliche Menge an Heap-Speicherplatz.

Auch in Java 7 leben internierte Zeichenfolgen nicht mehr in PermGen, sondern in Heap. Sie müssen sich also keine Gedanken über die Größe machen und ja, es wird Müll gesammelt:

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.

xli
quelle
Ich muss das unterstützen: Auf meiner Software zeigte ein Heap-Dump, dass der größte Teil des Heap-Speicherplatzes von StringInstanzen verwendet wurde. Als ich mir ihren Inhalt ansah, sah ich viele Duplikate und entschied mich, zu wechseln intern(), was Hunderte von MB sparte.
Matthieu
4

Gibt es Nebenwirkungen, die im Javadoc nicht erwähnt werden, dh mehr oder weniger Optimierung durch den JIT-Compiler?

Ich weiß nichts über die JIT-Ebene, aber es gibt eine direkte Bytecode-Unterstützung für den Zeichenfolgenpool , der magisch und effizient mit einer dedizierten CONSTANT_String_infoStruktur implementiert wird (im Gegensatz zu den meisten anderen Objekten mit allgemeineren Darstellungen).

JVMS

JVMS 7 5.1 sagt :

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 ist, 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

Es ist auch lehrreich, sich die Bytecode-Implementierung in OpenJDK 7 anzusehen.

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 hat eine dedizierte CONSTANT_String_info- Struktur, im Gegensatz zu regulären Objekten (znew 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:

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

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

Ich würde intern und == - Vergleich anstelle von gleich nur im Fall von Gleichheitsvergleich als Engpass bei mehreren Vergleichen von Zeichenfolgen untersuchen. Es ist sehr unwahrscheinlich, dass dies bei einer geringen Anzahl von Vergleichen hilft, da intern () nicht kostenlos ist. Nach dem aggressiven Internieren von Zeichenfolgen werden Aufrufe von intern () immer langsamer.

Mikko Maunu
quelle
2

Eine Art Speicherverlust kann durch die Verwendung entstehen, subString()wenn das Ergebnis im Vergleich zur Quellzeichenfolge klein ist und das Objekt eine lange Lebensdauer hat.

Die normale Lösung ist die Verwendung. new String( s.subString(...))Wenn Sie jedoch eine Klasse haben, die das Ergebnis eines potenziellen / wahrscheinlichen subString(...)Ereignisses speichert und keine Kontrolle über den Aufrufer hat, können Sie intern()die an den Konstruktor übergebenen String-Argumente speichern . Dies gibt den potentiell großen Puffer frei.

eremmel
quelle
Interessant, aber vielleicht ist dies implementierungsabhängig.
Akostadinov
1
Der oben erwähnte potenzielle Speicherverlust tritt in Java 1.8 und 1.7.06 (und neueren Versionen) nicht auf. Siehe Änderungen an der internen Darstellung von Zeichenfolgen in Java 1.7.0_06 .
Eremmel
Dies bestätigt, dass Mikrooptimierungen nur dann angewendet werden dürfen, wenn dies nach einer Leistungs- und / oder Speicherprofilerstellung erforderlich ist. Danke dir.
Akostadinov
2

Das Internieren von Zeichenfolgen ist nützlich, wenn die equals()Methode häufig aufgerufen wird, da die equals()Methode eine schnelle Überprüfung durchführt, um festzustellen, ob die Objekte zu Beginn der Methode identisch sind.

if (this == anObject) {
    return true;
}

Dies tritt normalerweise auf, wenn beim Durchsuchen eines Collectionanderen Codes möglicherweise auch Zeichenfolgengleichheitsprüfungen durchgeführt werden.

Das Internieren ist jedoch mit Kosten verbunden. Ich habe ein Mikrobenchmark für Code erstellt und festgestellt, dass der Internierungsprozess die Laufzeit um den Faktor 10 erhöht.

Der beste Ort für die Internierung ist normalerweise, wenn Sie Schlüssel lesen, die außerhalb des Codes gespeichert sind, da Zeichenfolgen im Code automatisch interniert werden. Dies geschieht normalerweise in der Initialisierungsphase Ihrer Anwendung, um die Strafe für den ersten Benutzer zu vermeiden.

Ein weiterer Ort, an dem dies möglich ist, ist die Verarbeitung von Benutzereingaben, die zum Durchführen von Schlüsselsuchen verwendet werden können. Dies geschieht normalerweise in Ihrem Anforderungsprozessor. Beachten Sie, dass die internierten Zeichenfolgen weitergegeben werden sollten.

Abgesehen davon macht es nicht viel Sinn, den Rest des Codes zu internieren, da dies im Allgemeinen keinen Nutzen bringt.

Archimedes Trajano
quelle
1

Ich würde dafür stimmen, dass es den Wartungsaufwand nicht wert ist.

In den meisten Fällen besteht keine Notwendigkeit und kein Leistungsvorteil, es sei denn, Ihr Code erledigt viel Arbeit mit Teilzeichenfolgen. In diesem Fall verwendet die String-Klasse die ursprüngliche Zeichenfolge plus einen Offset, um Speicherplatz zu sparen. Wenn Ihr Code häufig Teilzeichenfolgen verwendet, kann dies vermutlich dazu führen, dass Ihr Speicherbedarf explodiert.

wm_eddie
quelle
1

http://kohlerm.blogspot.co.uk/2009/01/is-javalangstringintern-really-evil.html

behauptet, dass String.equals()verwendet wird "==", um StringObjekte vorher zu vergleichen , nach

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

Es vergleicht die Länge der Strings und dann den Inhalt.

(Übrigens können Produktcode-Zeichenfolgen in einem Verkaufskatalog alle gleich lang sein - BIC0417 ist ein Schutzhelm für Radfahrer, TIG0003 ist ein lebender erwachsener männlicher Tiger - Sie benötigen wahrscheinlich alle Arten von Lizenzen, um eine davon zu bestellen Vielleicht bestellen Sie besser gleichzeitig einen Schutzhelm.)

Es hört sich also so an, als ob Sie einen Vorteil daraus ziehen, Ihre Strings durch ihre intern()Version zu ersetzen , aber Sie erhalten Sicherheit - und Lesbarkeit und Standardkonformität - ohne "==" für equals()in Ihrer Programmierung. Und das meiste, was ich sagen werde, hängt davon ab, ob es wahr ist, wenn es wahr ist.

Aber testet String.equals(), dass Sie ihm einen String und kein anderes Objekt übergeben haben, bevor Sie ihn verwenden "=="? Ich bin nicht qualifiziert zu sagen, aber ich würde nicht raten, da die meisten dieser equals()Operationen überwiegend von String zu String ausgeführt werden, so dass der Test fast immer bestanden wird. In der Tat String.equals()impliziert die Priorisierung von "==" im Inneren die Gewissheit, dass Sie den String häufig mit demselben tatsächlichen Objekt vergleichen.

Ich hoffe, niemand ist überrascht, dass die folgenden Zeilen ein Ergebnis von "false" ergeben:

    Integer i = 1;
    System.out.println("1".equals(i));

Aber wenn Sie in die zweite Zeile wechseln i, ist i.toString()es natürlich true.

Zu den Orten, an denen Sie auf einen Nutzen aus dem Praktikum hoffen können, gehören Setund Mapnatürlich. Ich hoffe, dass bei internierten Strings die Hashcodes zwischengespeichert sind ... Ich denke, das wäre eine Voraussetzung. Und ich hoffe, ich habe nicht nur eine Idee verschenkt, mit der ich eine Million Dollar verdienen könnte. :-)

Was den Speicher betrifft, ist es auch offensichtlich, dass dies eine wichtige Grenze ist, wenn Ihr String-Volumen groß ist oder wenn Sie möchten, dass der von Ihrem Programmcode verwendete Speicher sehr klein ist. Wenn Ihr Volumen an -distinct- Strings sehr groß ist, ist es möglicherweise an der Zeit, dedizierten Datenbankprogrammcode für die Verwaltung und einen separaten Datenbankserver zu verwenden. Ebenso, wenn Sie ein kleines Programm verbessern können (das in 10000 Instanzen gleichzeitig ausgeführt werden muss), indem es seine Strings selbst überhaupt nicht speichert.

Es ist verschwenderisch, einen neuen String zu erstellen und ihn dann sofort als intern()Ersatz zu verwerfen , aber es gibt keine klare Alternative, außer den doppelten String beizubehalten. Die Ausführungskosten bestehen also darin, im internen Pool nach Ihrer Zeichenfolge zu suchen und dann dem Garbage Collector zu ermöglichen, das Original zu entsorgen. Und wenn es ein String-Literal ist, dann kommt es sowieso schon interniert.

Ich frage mich, ob intern()durch bösartigen Programmcode missbraucht werden kann, um festzustellen, ob einige Zeichenfolgen und ihre Objektreferenzen bereits im intern()Pool vorhanden sind und daher an anderer Stelle in der Java-Sitzung vorhanden sind, wenn dies nicht bekannt sein sollte. Aber das wäre nur möglich, wenn der Programmcode bereits vertrauensvoll verwendet wird, denke ich. Bei den Bibliotheken von Drittanbietern, die Sie in Ihr Programm aufnehmen, sollten Sie jedoch Ihre ATM-PIN-Nummern speichern und speichern!

Robert Carnegie
quelle
0

Der wahre Grund für die Verwendung von Praktikanten ist nicht der oben genannte. Sie können es verwenden, nachdem Sie einen Speicherfehler festgestellt haben. Viele der Zeichenfolgen in einem typischen Programm sind String.substring () einer anderen großen Zeichenfolge [Denken Sie daran, einen Benutzernamen aus einer 100K-XML-Datei zu entfernen. Die Java-Implementierung besteht darin, dass der Teilstring einen Verweis auf den ursprünglichen String und den Start + das Ende in diesem riesigen String enthält. (Der Gedanke dahinter ist eine Wiederverwendung derselben großen Saite)

Nach 1000 großen Dateien, von denen Sie nur 1000 Kurznamen speichern, behalten Sie die gesamten 1000 Dateien im Speicher! Lösung: Verwenden Sie in diesem Szenario einfach smallsubstring.intern ()

asaf
quelle
Warum nicht einfach einen neuen String aus dem Teilstring erstellen, wenn Sie ihn brauchen?
Thorbjørn Ravn Andersen
0

Ich verwende intern, um Speicher zu speichern. Ich habe eine große Menge an String-Daten im Speicher und bei der Verwendung von intern () wurde eine enorme Menge an Speicher gespeichert. Obwohl viel weniger Speicher verwendet wird, wird der verwendete Speicher leider im PermGen-Speicher und nicht im Heap gespeichert, und es ist schwierig, den Kunden zu erklären, wie die Zuweisung dieses Speichertyps erhöht werden kann.

Gibt es also eine Alternative zu intern (), um den Speicherverbrauch zu reduzieren (== versus Leistungsvorteile sind für mich kein Problem)?

Paul Taylor
quelle
0

Seien wir ehrlich: Das Hauptanwendungsszenario besteht darin, dass Sie einen Datenstrom (entweder über einen Eingabestream oder aus einem JDBC-Ergebnisset) lesen und es gibt eine Vielzahl kleiner Zeichenfolgen, die sich durchgehend wiederholen.

Hier ist ein kleiner Trick, mit dem Sie steuern können, mit welchem ​​Mechanismus Sie Strings und andere unveränderliche Elemente verinnerlichen möchten, sowie eine Beispielimplementierung:

/**
 * Extends the notion of String.intern() to different mechanisms and
 * different types. For example, an implementation can use an
 * LRUCache<T,?>, or a WeakHashMap.
 */
public interface Internalizer<T> {
    public T get(T obj);
}
public static class LRUInternalizer<T> implements Internalizer<T> {
    private final LRUCache<T, T> cache;
    public LRUInternalizer(int size) {
        cache = new LRUCache<T, T>(size) {
            private static final long serialVersionUID = 1L;
            @Override
            protected T retrieve(T key) {
                return key;
            }
        };
    }
    @Override
    public T get(T obj) {
        return cache.get(obj);
    }
}
public class PermGenInternalizer implements Internalizer<String> {
    @Override
    public String get(String obj) {
        return obj.intern();
    }
}

Ich benutze das oft, wenn ich Felder aus Streams oder aus ResultSets lese. Hinweis: LRUCacheist ein einfacher Cache, der auf basiert LinkedHashMap<K,V>. Es ruft automatisch den vom Benutzer bereitgestellten anretrieve() Methode für alle Cache-Fehler auf.

Die Möglichkeit, dies zu verwenden, besteht darin, LRUInternalizervor dem Lesen (oder vor dem Lesen) einen zu erstellen , damit Strings und andere kleine unveränderliche Objekte zu verinnerlichen und dann freizugeben. Beispielsweise:

Internalizer<String> internalizer = new LRUInternalizer(2048);
// ... get some object "input" that stream fields
for (String s : input.nextField()) {
    s = internalizer.get(s);
    // store s...
}
Pierre D.
quelle
0

Ich benutze es, um den Inhalt von ungefähr 36000 Codes zwischenzuspeichern, die mit zugehörigen Namen verknüpft sind. Ich interniere die Zeichenfolgen im Cache, weil viele der Codes auf dieselbe Zeichenfolge verweisen.

Durch das Internieren der Zeichenfolgen in meinem Cache stelle ich sicher, dass Codes, die auf dieselbe Zeichenfolge verweisen, tatsächlich auf denselben Speicher verweisen, wodurch mir RAM-Speicherplatz gespart wird.

Wenn die internierten Strings tatsächlich Müll gesammelt würden, würde es für mich überhaupt nicht funktionieren. Dies würde den Zweck des Praktikums grundsätzlich zunichte machen. Meins wird nicht durch Müll gesammelt, da ich einen Verweis auf jede einzelne Zeichenfolge im Cache habe.

Rodney P. Barbati
quelle
Nein, alle internierten gleichen Zeichenfolgen, die sich zu einem bestimmten Zeitpunkt im Speicher befinden, sind immer noch dasselbe Objekt. Es handelt sich um ein anderes Objekt als die gleiche Zeichenfolge, die sich vor der Speicherbereinigung im Speicher befand. Dies ist jedoch kein Problem, da die alte Zeichenfolge nicht mehr vorhanden ist.
Bdruemen
0

Die Kosten für die Internierung eines Strings sind viel höher als die Zeit, die bei einem Vergleich mit einem einzelnen stringA.equals (B) eingespart wird. Verwenden Sie es (aus Leistungsgründen) nur, wenn Sie wiederholt dieselben unveränderten Zeichenfolgenvariablen verwenden. Wenn Sie beispielsweise regelmäßig eine stabile Liste von Zeichenfolgen durchlaufen, um einige Karten zu aktualisieren, die im selben Zeichenfolgenfeld eingegeben wurden, können Sie eine schöne Speicherung erzielen.

Ich würde vorschlagen, String-Interning zu verwenden, um die Leistung zu optimieren, wenn Sie bestimmte Teile Ihres Codes optimieren.

Denken Sie auch daran, dass String unveränderlich sind und nicht den dummen Fehler machen

String a = SOME_RANDOM_VALUE
a.intern()

Denken Sie daran

String a = SOME_RANDOM_VALUE.intern()
Murren
quelle
0

Wenn Sie nach einem unbegrenzten Ersatz für String.intern suchen, auch Müll gesammelt, funktioniert das Folgende gut für mich.

private static WeakHashMap<String, WeakReference<String>> internStrings = new WeakHashMap<>();
public static String internalize(String k) {
    synchronized (internStrings) {
        WeakReference<String> weakReference = internStrings.get(k);
        String v = weakReference != null ? weakReference.get() : null;
        if (v == null) {
            v = k;
            internStrings.put(v, new WeakReference<String>(v));
        }
        return v;
    }
}

Wenn Sie ungefähr abschätzen können, wie viele verschiedene Zeichenfolgen es geben wird, verwenden Sie einfach String.intern () mit -XX: StringTableSize = highEnoughValue .

bdruemen
quelle
SoftRef würde mehr Sinn machen.
Vach
@vach Durch die Verwendung von WeakReference (anstelle von SoftReference) wird der Speicher früher freigegeben, sodass andere Zuordnungen möglicherweise schneller erfolgen. Es hängt davon ab, was die Anwendung sonst noch tut. Beides könnte sinnvoll sein.
Bdruemen