Is het goede praktijk om java.lang.string.intern () te gebruiken?

De Javadoc over String.intern()geeft niet veel details. (In een notendop: het retourneert een canonieke weergave van de string, waardoor de geïnterneerde snaren worden vergeleken met behulp van ==)

  • Wanneer zou ik deze functie gebruiken ten gunste van String.equals()?
  • Zijn er bijwerkingen die niet worden vermeld in de Javadoc, d.w.z. min of meer optimalisatie door de JIT-compiler?
  • zijn er verder gebruik van String.intern()?

Antwoord 1, Autoriteit 100%

Dit heeft (bijna) niets te maken met stringvergelijking. string Interning is bedoeld voor het opslaan van geheugen als u veel snaren hebt met dezelfde inhoud in u toepassing. Door het gebruik van String.intern()De applicatie heeft slechts één exemplaar op de lange termijn en een bijwerking is dat u een snelle referentie-gelijkheidsvergelijking kunt uitvoeren in plaats van gewone reeksvergelijking (maar dit is meestal niet aan te raden Omdat het echt gemakkelijk te breken is door te vergeten om slechts één enkele instantie te starten).


Antwoord 2, Autoriteit 65%

Wanneer zou ik deze functie gebruiken ten gunste van string.equals ()

Wanneer u Speed ​​nodig hebt, aangezien u de reeksen door verwijzing kunt vergelijken (== is sneller dan gelijken)

Zijn er bijwerkingen die niet worden vermeld in de Javadoc?

Het primaire nadeel is dat je moet onthouden om ervoor te zorgen dat je eigenlijk stagiair () alle snaren die je gaat vergelijken. Het is gemakkelijk om te vergeten om te intern () alle snaren en dan kunt u verwarde onjuiste resultaten krijgen. Ook, voor iedereen, zorg er dan voor dat je heel duidelijk documenteert dat je afhankelijk bent van de snaren die worden geïnternaliseerd.

Het tweede nadeel als u besluit om snaren te internaliseren, is dat de methode van de stagiair () relatief duur is. Het moet het zwembad van unieke snaren beheren, dus het doet een behoorlijk stukje werk (zelfs als de tekenreeks al is geïnternaliseerd). Dus wees voorzichtig in uw codeontwerp, zodat u b.v. stagiair () Alle geschikte snaren op ingang, zodat u er geen zorgen over hoeft te maken.

(van jguru)

Derde nadeel (alleen Java 7 of minder): Intern baren wonen in PermGen-ruimte, die meestal vrij klein is; U kunt een OUTOFMEMORYError tegenkomen met veel gratis heapruimte.

(van Michael Borgwardt)


Antwoord 3, Autoriteit 20%

String.intern()is absoluut vuilnis verzameld in moderne JVM’s.
Het volgende ontslaat nooit uit het geheugen, vanwege GC-activiteit:

// 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();
        }
    }
}

Zie meer (van mij) op de mythe of non Gced string.intern () .


Antwoord 4, Autoriteit 8%

Ik heb onlangs een artikel geschreven over string.intern () implementatie in Java 6, 7 en 8:
string.intern in Java 6, 7 en 8 – String Pooling .

Ik hoop dat het voldoende informatie moet bevatten over de huidige situatie met een koordpooling in Java.

In een notendop:

  • Vermijd String.intern()in Java 6, omdat het in PersoGen gaat
  • Liever String.intern()in Java 7 & AMP; Java 8: Het gebruikt 4-5x minder geheugen dan het rollen van je eigen objectpool
  • Zorg ervoor dat u -XX:StringTableSize(de standaardinstelling is waarschijnlijk te klein; stel een priemgetal in)

Antwoord 5, Autoriteit 7%

Vergelijking van strings met == is veel sneller dan met gelijken ()

5 Tijd sneller, maar aangezien stringvergelijking gewoonlijk slechts een klein percentage van de totale uitvoeringstijd van een toepassing vertegenwoordigt, is de totale winst veel kleiner dan dat, en de uiteindelijke winst zal worden verdund tot een paar procent.

String.intern() trek de string weg van Heap en plaats deze in PermGen

Internaliseerde strings worden in een ander opslaggebied geplaatst: Permanente generatiewat een gebied is van de JVM die is gereserveerd voor niet-gebruikersobjecten, zoals klassen, methoden en andere interne JVM-objecten. De grootte van dit gebied is beperkt en het is veel kostbaarder dan een hoop. Omdat dit gebied kleiner is dan Heap, is de kans groter dat alle ruimte wordt gebruikt en een OutOfMemoryException wordt verkregen.

String.intern()-tekenreeks wordt door vuilnis verzameld

In de nieuwe versies van JVM worden ook geïnternaliseerde strings verzameld als er niet door een object naar wordt verwezen.

Rekening houdend met het bovenstaande 3 punt zou je kunnen afleiden dat String intern() alleen nuttig kan zijn in een paar situaties waarin je veel strings vergelijkt, maar het is beter om geen interne string te gebruiken als je het niet weet precies wat u doet …


Antwoord 6, autoriteit 4%

Wanneer zou ik deze functie gebruiken in het voordeel van String.equals()

Aangezien ze verschillende dingen doen, waarschijnlijk nooit.

Het invoegen van strings om prestatieredenen, zodat je ze kunt vergelijken voor referentiegelijkheid, heeft alleen zin als je een tijdje verwijzingen naar de strings vasthoudt – strings die afkomstig zijn van gebruikersinvoer of IO worden niet geïnterneerd.

Dat betekent dat u in uw toepassing invoer ontvangt van een externe bron en deze verwerkt tot een object met een semantische waarde – bijvoorbeeld een identifier – maar dat object heeft een type dat niet te onderscheiden is van de onbewerkte gegevens, en heeft andere regels over hoe de programmeur zou het moeten gebruiken.

Het is bijna altijd beter om een ​​UserId-type aan te maken dat is geïnterneerd (het is gemakkelijk om een ​​thread-safe generiek interningmechanisme te maken) en werkt als een open opsomming, dan de java.lang.Stringtype met referentiesemantiek als het toevallig een gebruikers-ID is.

Op die manier krijg je geen verwarring of een bepaalde String wel of niet is geïnterneerd, en kun je eventueel aanvullend gedrag dat je nodig hebt in de open opsomming opnemen.


Antwoord 7, autoriteit 3%

Ik ken geen voordelen, en als die er waren, zou ik denken dat equals() zelf intern() intern() zou gebruiken (wat niet het geval is).

De mythen van stagiair(e) ontkrachten


Antwoord 8, autoriteit 2%

Daniel Brückner heeft volkomen gelijk. String interning is bedoeld om geheugen (heap) te besparen.Ons systeem heeft momenteel een gigantische hashmap voor het bewaren van bepaalde gegevens. Naarmate het systeem schaalt, zal de hashmap groot genoeg zijn om de hoop uit het geheugen te halen (zoals we hebben getest). Door alle gedupliceerde strings van alle objecten in de hashmap te interneren, bespaart het ons een aanzienlijke hoeveelheid heapruimte.

Ook in Java 7 leven geïnterneerde strings niet lang meer in PermGen maar in plaats daarvan ophopen.U hoeft zich dus geen zorgen te maken over de grootte en ja, het wordt verzameld met afval:

In JDK 7 worden interne strings niet langer toegewezen in de permanente
generatie van de Java-heap, maar worden in plaats daarvan toegewezen in de main
een deel van de Java-hoop (bekend als de jonge en oude generaties), langs
met de andere objecten die door de toepassing zijn gemaakt. Deze verandering zal
resulteren in meer gegevens die zich op de Java-hoofdhoop bevinden en minder gegevens in
de permanente generatie, en het kan dus nodig zijn dat de grootte van de heap
bijgestelde. De meeste toepassingen zullen slechts relatief kleine verschillen zien
in heapgebruik als gevolg van deze wijziging, maar grotere applicaties die worden geladen
veel klassen of intensief gebruik maken van de String.intern() methode zal zien
meer significante verschillen.


Antwoord 9, autoriteit 2%

Zijn er bijwerkingen die niet in de Javadoc worden genoemd, d.w.z. min of meer optimalisatie door de JIT-compiler?

Ik weet niets van het JIT-niveau, maar er is directe bytecode-ondersteuning voor de string-pool, die op magische wijze en efficiënt is geïmplementeerd met een speciale CONSTANT_String_infostruct ( in tegenstelling tot de meeste andere objecten die meer algemene representaties hebben).

JVMS

JVMS 7 5.1 zegt:

Een letterlijke tekenreeks is een verwijzing naar een instantie van klasse String en is afgeleid van een CONSTANT_String_info-structuur (§4.4.3) in de binaire representatie van een klasse of interface. De CONSTANT_String_info-structuur geeft de reeks Unicode-codepunten die de letterlijke tekenreeks vormen.

De Java-programmeertaal vereist dat identieke letterlijke tekenreeksen (dat wil zeggen letterlijke waarden die dezelfde reeks codepunten bevatten) moeten verwijzen naar dezelfde instantie van klasse String (JLS §3.10.5). Bovendien, als de methode String.intern wordt aangeroepen op een tekenreeks, is het resultaat een verwijzing naar dezelfde klasse-instantie die zou worden geretourneerd als die tekenreeks als een letterlijke tekenreeks zou verschijnen. De volgende expressie moet dus de waarde true hebben:

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

Om een ​​letterlijke tekenreeks af te leiden, onderzoekt de Java Virtual Machine de reeks codepunten die wordt gegeven door de CONSTANT_String_info-structuur.

  • Als de methode String.intern eerder is aangeroepen op een instantie van de klasse String die een reeks Unicode-codepunten bevat die identiek zijn aan die gegeven door de CONSTANT_String_info-structuur, dan is het resultaat van de letterlijke afleiding van de tekenreeks een verwijzing naar die dezelfde instantie van klasse String.

  • Anders wordt er een nieuwe instantie van klasse String gemaakt die de reeks Unicode-codepunten bevat die worden gegeven door de CONSTANT_String_info-structuur; een verwijzing naar die klasse-instantie is het resultaat van letterlijke afleiding van tekenreeksen. Ten slotte wordt de interne methode van de nieuwe String-instantie aangeroepen.

Bytecode

Het is ook leerzaam om te kijken naar de bytecode-implementatie op OpenJDK 7.

Als we decompileren:

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);
    }
}

we hebben op de constante pool:

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

en 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

Let op hoe:

  • 0en 3: dezelfde ldc #2constante wordt geladen (de letterlijke waarden)
  • 12: er wordt een nieuwe string-instantie gemaakt (met #2als argument)
  • 35aen cworden vergeleken als regelmatige voorwerpen met if_acmpne

De vertegenwoordiging van constante strings is heel magie op de bytecode:

  • het heeft een speciale CONSTANT_String_info structuur, in tegenstelling tot regelmatige voorwerpen (bijvoorbeeld new String)
  • de structuur wijst naar een CONSTANT_Utf8_info Structure die de gegevens bevat. Dat is de enige gegevens die nodig zijn om de string te vertegenwoordigen.

en de JVM citaat hierboven lijkt te zeggen dat wanneer UTF8 wordt gewezen op dezelfde, dan identieke exemplaren worden geladen door ldc.

Ik heb soortgelijke tests uitgevoerd voor velden, en:

  • static final String s = "abc"verwijst naar de constante tabel via ConstantValue Attribute
  • non-finale velden niet hebben dat attribuut, maar kan nog steeds worden geïnitialiseerd met ldc

Bonus : Vergelijk dat met de Integer zwembad , die heeft geen directe steun bytecode (dwz geen CONSTANT_String_infoanaloog).


Antwoord 10

Ik zou intern en onderzoeken == – vergelijking in plaats van gelijken alleen in het geval van gelijken-vergelijking wezen knelpunt in meerdere vergelijkingen van de string. Dit is zeer onwaarschijnlijk om te helpen met een klein aantal vergelijkingen, omdat intern () is niet gratis. Na agressief interneren snaren kunt u bellen naar stagiair () steeds trager te vinden.


Antwoord 11

Een soort geheugen lekken kunnen afkomstig zijn van het gebruik van subString()wanneer het resultaat is klein in vergelijking met de bron string en het object een lange levensduur.

De normale oplossing is om new String( s.subString(...))gebruiken, maar als je een klasse die slaat het resultaat van een mogelijke / waarschijnlijke subString(...)en hebben geen controle over de beller, kunt u overwegen om de intern()op te slaan van de String argumenten doorgegeven aan de aannemer. Dit geeft de mogelijkheid grote buffer.


Antwoord 12

String interning is handig in het geval dat de equals()methode wordt vaak ingeroepen omdat de equals()methode doet een snelle controle om te zien of de voorwerpen gelijk aan het begin van de methode.

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

Dit gebeurt meestal op bij het zoeken door middel van een Collectionhoewel andere code kan ook doen reeks controles gelijkheid.

er kosten verbonden aan stage hoewel ik voerde een microbenchmark van wat code en vond dat de stage proces verhoogt de looptijd met een factor 10.

De beste plaats om de stage te doen is meestal als je aan het lezen bent sleutels die buiten de code worden opgeslagen als strings in de code automatisch worden geïnterneerd. Dit zou normaal gesproken gebeuren op de initialisatie stadia van uw aanvraag met het oog op de first-gebruiker penalty te voorkomen.

Een andere plaats waar het kan worden gedaan wanneer de verwerking gebruikersinvoer die kunnen worden gebruikt om belangrijke opzoeken doen. Dit gebeurt normaal gesproken in uw verzoek processor, er rekening mee dat de geïnterneerde strings moeten worden doorgegeven.

Afgezien van dat er niet veel zin te doen interneren in de rest van de code in het algemeen geen voordeel geven.


Antwoord 13

Ik zou stemmen op dat het niet de moeite waard het onderhoud gedoe.

De meeste van de tijd, zal er geen behoefte aan, en geen prestatie voordeel, tenzij je code doet veel van het werk met substrings zijn. In dat geval zal de klasse String de originele string plus een offset om geheugen te besparen. Als uw code gebruik substrings veel, dan vermoed ik dat het gewoon zal ertoe leiden dat uw geheugen eisen te ontploffen.


Antwoord 14

http://kohlerm.blogspot.co. VK / 2009/01 / IS-JavalangStringintern-Really-Evil.html

Beweert dat String.equals()gebruikt "=="om te vergelijken StringObjects vóór, volgens

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

Het vergelijkt de lengtes van snaren en vervolgens de inhoud.

(trouwens, productcodekoorden in een verkoopcatalogus zijn aansprakelijk voor alle lengte – BIC0417 is een veiligheidshelm van een fietscist, TIG0003 is een live-volwassen mannelijke tijger –
U hebt waarschijnlijk allerlei licenties nodig om een ​​van die te bestellen. En misschien kunt u tegelijkertijd een veiligheidshelm bestellen.)

Dus het klinkt alsof je een voordeel krijgt van het vervangen van je snaren met hun intern()-versie, maar je krijgt veiligheids- en leesbaarheid en standaardcalveatie – – zonder te gebruiken “==” voor equals()in uw programmering. En de meeste van wat ik ga zeggen, hangt ervan af dat het waar is, als het waar is.

Maar doet String.equals()Test die je een string hebt geslaagd en niet een ander object, voordat je "=="gebruikt? Ik ben niet gekwalificeerd om te zeggen, maar ik zou het niet raden, omdat overweldigend het meest zulke equals()operations zal strikken naar string, zodat de test bijna altijd is gepasseerd. Inderdaad, prioriteren “==” inside String.equals()impliceert een vertrouwen dat u vaak de reeks vergelijkt met hetzelfde feitelijke object.

Ik hoop dat niemand verrast is dat de volgende lijnen een gevolg zijn van “false”:

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

Maar als u iwijzigt naar i.toString()in de tweede regel, is het natuurlijk true.

Locaties waar u zou hopen op een voordeel van Interning omvatten Seten Map, uiteraard. Ik hoop dat de geïnterneerde snaren hun hashcodes cache hebben … ik denk dat dat een vereiste zou zijn. En ik hoop dat ik het niet alleen een idee heb gegeven dat me een miljoen dollar zou kunnen verdienen. : -)

Wat betreft het geheugen, het is ook duidelijk dat dit een belangrijke limiet is als uw volume van de snaren groot is, of als u wilt dat het geheugen dat door uw programmacode wordt gebruikt om erg klein te zijn. Als uw volume-Distinct-strings erg groot is, dan is het mogelijk tijd om te overwegen met behulp van dedicated database-programmacode om ze te beheren en een afzonderlijke databaseserver. Evenzo, als u een klein programma kunt verbeteren (dat nodig heeft om tegelijkertijd in 10000-instanties te worden uitgevoerd) door het helemaal niet op te slaan.

Het voelt verspilling om een ​​nieuwe touw te maken en gooi het vervolgens meteen weg voor zijn intern()Subtitute, maar er is geen duidelijk alternatief, behalve voor het houden van de dubbele string. Dus echt de uitvoering van de uitvoering van het zoeken naar uw string in het intern zwembad en vervolgens de vuilnisbak toestaan ​​om het origineel te verwijderen. En als het een string-letterlijk is, komt het toch intern.

Ik vraag me af of intern()kan worden misbruikt door schadelijke programmacode om te detecteren of er al een reeks van een reeks en hun objecten in de intern()zwembad, en Er bestaat daarom elders in de Java-sessie, wanneer dat niet bekend zou zijn. Maar dat zou alleen mogelijk zijn wanneer de programmacode al op een vertrouwde manier wordt gebruikt, denk ik. Toch is het iets om te overwegen over de bibliotheken van derden die u in uw programma opneemt om uw ATM-PIN-nummers op te slaan en te onthouden!


Antwoord 15

De echte reden om stagiair te gebruiken is niet het bovenstaande.
U kunt het gebruiken nadat u geen fout hebt gemaakt. Veel van de tekenreeks in een typisch programma zijn string.substring () van andere grote snaar [Denk aan een gebruikersnaam uit een 100k XML-bestand.
De Java-implementatie is dat de substring een verwijzing naar de oorspronkelijke reeks bevat en het begin + einde in die enorme reeks. (De gedachte achter het is een hergebruik van dezelfde grote string)

Na 1000 grote bestanden, waarvan u slechts 1000 korte namen opslaat, bewaart u de hele 1000 bestanden in het geheugen!
Oplossing: gebruik in dit scenario gewoon smallsubstring.intern()


Antwoord 16

Ik gebruik intern om geheugen te besparen, ik bewaar een grote hoeveelheid String-gegevens in het geheugen en overgaand op het gebruik van intern() bespaarde ik een enorme hoeveelheid geheugen. Helaas, hoewel het veel minder geheugen gebruikt, wordt het geheugen dat het gebruikt, opgeslagen in PermGen-geheugen en niet in Heap, en het is moeilijk om aan klanten uit te leggen hoe de toewijzing van dit type geheugen kan worden verhoogd.

Dus is er een alternatief voor intern() om het geheugenverbruik te verminderen, (de == versus gelijk aan prestatievoordelen is geen probleem voor mij)


Antwoord 17

Laten we eerlijk zijn: het belangrijkste gebruiksscenario is wanneer je een gegevensstroom leest (ofwel via een invoerstroom, of van een JDBC ResultSet) en er is een groot aantal kleine strings die overal worden herhaald.

p>

Hier is een kleine truc die je enige controle geeft over wat voor soort mechanisme je wilt gebruiken om Strings en andere onveranderlijke elementen te internaliseren, en een voorbeeldimplementatie:

/**
 * 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();
    }
}

Ik gebruik dat vaak als ik velden uit streams of uit ResultSets lees.
Opmerking: LRUCacheis een eenvoudige cache gebaseerd op LinkedHashMap<K,V>. Het roept automatisch de door de gebruiker geleverde retrieve()methode aan voor alle cache missers.

De manier om dit te gebruiken is door één LRUInternalizerte maken voordat je leest (of leest), deze gebruikt om Strings en andere kleine onveranderlijke objecten te internaliseren en deze vervolgens vrij te maken. Bijvoorbeeld:

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

Antwoord 18

Ik gebruik het om de inhoud van ongeveer 36000 codes die naar verwante namen verwijzen, in de cache op te slaan. Ik intern de strings in de cache omdat veel van de codes naar dezelfde string verwijzen.

Door de strings in mijn cache te interneren, zorg ik ervoor dat codes die naar dezelfde string verwijzen, ook daadwerkelijk naar hetzelfde geheugen verwijzen, waardoor ik RAM-ruimte bespaar.

Als de geïnterneerde strings daadwerkelijk verzameld afval waren, zou het voor mij helemaal niet werken. Dit zou in feite het doel van internering tenietdoen. De mijne wordt niet verzameld omdat ik een verwijzing naar elke string in de cache heb.


Antwoord 19

De kosten van het interneren van een string zijn veel meer dan de tijd die wordt bespaard in een enkele stringA.equals(B)-vergelijking. Gebruik het alleen (om prestatieredenen) wanneer u herhaaldelijk dezelfde ongewijzigde tekenreeksvariabelen gebruikt. Als u bijvoorbeeld regelmatig een stabiele lijst met tekenreeksen doorloopt om enkele kaarten die op hetzelfde tekenreeksveld zijn ingetoetst, bij te werken, kunt u een mooie besparing krijgen.

Ik raad aan om string-interning te gebruiken om de prestaties aan te passen wanneer je specifieke delen van je code optimaliseert.

Onthoud ook dat String onveranderlijk is en maak niet de domme fout van

String a = SOME_RANDOM_VALUE
a.intern()

vergeet niet te doen

String a = SOME_RANDOM_VALUE.intern()

Antwoord 20

Als je op zoek bent naar een onbeperkte vervanger voor String.intern, ook wel vuilnis verzameld, werkt het volgende goed voor mij.

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;
    }
}

Natuurlijk, als je ruwweg kunt inschatten hoeveel verschillende strings er zullen zijn, gebruik dan gewoon String.intern() met -XX:StringTableSize=highEnoughValue.

Other episodes