È buona norma utilizzare java.lang.String.intern ()?


194

Javadoc String.intern()non fornisce molti dettagli. (In poche parole: restituisce una rappresentazione canonica della stringa, consentendo di confrontare le stringhe internate utilizzando ==)

  • Quando dovrei usare questa funzione a favore String.equals()?
  • Ci sono effetti collaterali non menzionati nel Javadoc, ovvero più o meno ottimizzazione da parte del compilatore JIT?
  • Ci sono ulteriori usi di String.intern()?

14
La chiamata a intern () ha il suo impatto sulle prestazioni, l'utilizzo di intern () per migliorare le prestazioni deve essere testato per assicurarsi che acceleri davvero il programma in modo significativo per valere la complessità aggiuntiva. È inoltre possibile utilizzare questo per ridurre il consumo di memoria per le tabelle di grandi dimensioni con valori ripetitivi in ​​modo affidabile. Tuttavia, in entrambi i casi ci sono altre opzioni che potrebbero essere migliori.
Peter Lawrey,

Sì, intern () ha il suo impatto sulle prestazioni. Soprattutto perché il costo intern () aumenta linearmente man mano che le stringhe interne e mantengono un riferimento ad esse. Almeno su un sole / oracolo 1.6.0_30 vm.
lacroix1547,

Risposte:


125

Quando dovrei usare questa funzione a favore di String.equals ()

quando hai bisogno di velocità poiché puoi confrontare le stringhe per riferimento (== è più veloce di uguale a)

Ci sono effetti collaterali non menzionati nel Javadoc?

Lo svantaggio principale è che devi ricordarti di assicurarti di fare intern () tutte le stringhe che stai per confrontare. È facile dimenticare di internare () tutte le stringhe e quindi si possono ottenere risultati erroneamente confusi. Inoltre, per il bene di tutti, assicurati di documentare molto chiaramente che stai facendo affidamento sulle stringhe che vengono interiorizzate.

Il secondo svantaggio se si decide di internalizzare le stringhe è che il metodo intern () è relativamente costoso. Deve gestire il pool di stringhe univoche, quindi fa un bel po 'di lavoro (anche se la stringa è già stata interiorizzata). Quindi, fai attenzione nella progettazione del codice in modo che, ad esempio, intern () tutte le stringhe appropriate sull'input in modo da non doverti più preoccupare.

(da JGuru)

Terzo svantaggio (solo Java 7 o meno): le stringhe internate vivono nello spazio PermGen, che di solito è piuttosto piccolo; potresti imbatterti in un OutOfMemoryError con molto spazio heap libero.

(da Michael Borgwardt)


64
Un terzo svantaggio: le stringhe internate vivono nello spazio PermGen, che di solito è piuttosto piccolo; potresti imbatterti in un OutOfMemoryError con molto spazio heap libero.
Michael Borgwardt,

15
Anche le VM più recenti di AFAIK raccolgono la spazzatura nello spazio PermGen.
Daniel Rikowski,

31
Lo stagista riguarda la gestione della memoria, non la velocità di confronto. La differenza tra if (s1.equals(s2))e if (i1 == i2)è minima a meno che tu non abbia molte stringhe lunghe con gli stessi personaggi principali. Nella maggior parte degli usi del mondo reale (diversi dagli URL) le stringhe differiranno tra i primi caratteri. E le lunghe catene if-else sono comunque un odore di codice: usa enum e mappe di funzioni.
kdgregory,

25
puoi comunque usare la sintassi s1.equals in tutto il programma, DONT use ==, .equals use == internamente per la valutazione del corto circuito
gtrak

15
Michael Borgwardt NON ha detto che le stringhe internate non possono essere raccolte. E questa è un'affermazione FALSE. Ciò che i commenti di Michael (correttamente) dicono è più sottile di così.
Stephen C,

193

Questo non ha (quasi) nulla a che fare con il confronto delle stringhe. Lo string interning ha lo scopo di risparmiare memoria se nell'applicazione sono presenti molte stringhe con lo stesso contenuto. Utilizzando String.intern()l'applicazione avrà solo un'istanza a lungo termine e un effetto collaterale è che puoi eseguire un confronto rapido dell'uguaglianza di riferimento invece del normale confronto di stringhe (ma questo non è generalmente consigliabile perché è davvero facile da rompere dimenticando solo l'internato una singola istanza).


4
Non è giusto. L'internamento delle stringhe si verifica sempre, automaticamente, quando viene valutata ogni espressione di stringa. C'è sempre una copia per ogni stringa univoca di caratteri utilizzati ed è "condivisa internamente" se si verificano più utilizzi. Chiamare String.intern () non rende tutto ciò possibile, ma restituisce solo la rappresentazione canonica interna. Vedi javadoc.
Glen Best,

16
È necessario chiarire: l'internamento avviene sempre automaticamente per le stringhe costanti in fase di compilazione (valori letterali ed espressioni fisse). Inoltre si verifica quando String.intern () viene chiamato su stringhe valutate dinamicamente in fase di runtime.
Glen Best,

Quindi vuoi dire, se ci sono 1000 oggetti di "Hello" in Heap e io eseguo intern () su uno di essi, allora gli altri 999 oggetti verranno distrutti automaticamente?
Arun Raaj,

@ArunRaaj no, avrete il vostro 1000 ancora sul mucchio, e uno in più nel pool interno, che può essere pronto per il riutilizzo al più tardi str.intern(), quando strè "Hello".
Matthieu,

37

String.intern()è sicuramente spazzatura raccolta nelle moderne JVM.
Il seguente MAI esaurisce la memoria a causa dell'attività del GC:

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

Vedi di più (da me) sul mito di non GCed String.intern () .


26
OutOfMemoryException- no, non il codice sopra, nel mio cervello : link all'articolo javaturning, che punta a questo articolo, che punta all'articolo javaturning, che ... :-)
user85421

Anche se puoi vedere che il post è stato modificato per aggiungere quel link;)
Riking

3
Potresti voler dire che sei anche tu l'autore del riferimento esterno a cui ti colleghi.
Thorbjørn Ravn Andersen,

11
@Carlos che collega un riferimento esterno che rimanda a StackOverflow dovrebbe causare un .. StackOverflow :)
Seiti

2
@Seiti I riferimenti circolari sono facilmente rilevabili in questi giorni: p
Ajay

16

Di recente ho scritto un articolo sull'implementazione di String.intern () in Java 6, 7 e 8: String.intern in Java 6, 7 e 8 - pooling di stringhe .

Spero che dovrebbe contenere abbastanza informazioni sulla situazione attuale con il pool di stringhe in Java.

In breve:

  • Evitare String.intern()in Java 6, perché va in PermGen
  • Preferisci String.intern()in Java 7 e Java 8: utilizza 4-5 volte meno memoria del roll-over del tuo pool di oggetti
  • Accordati -XX:StringTableSize(probabilmente il valore predefinito è troppo piccolo; imposta un numero primo)

3
Per favore, non solo pubblicare collegamenti al tuo blog, questo è considerato da alcuni come SPAM. Inoltre, i collegamenti ai blog hanno una notevole tendenza a morire di 404 morti. Riassumi il tuo articolo qui in linea o lascia quel link in un commento alla domanda.
Mat

3
Grazie per aver scritto che @ mik1! Articolo molto informativo, chiaro e aggiornato. (Sono tornato qui con l'intenzione di pubblicare un link da solo.)
Luke Usherwood

1
Grazie per aver menzionato il -XX arg. Puoi anche usarlo per vedere le statistiche della tabella: -XX: + PrintStringTableStatistics
csadler

13

Il confronto delle stringhe con == è molto più veloce rispetto a equals ()

5 Tempo più veloce, ma dal momento che il confronto delle stringhe di solito rappresenta solo una piccola percentuale del tempo totale di esecuzione di un'applicazione, il guadagno complessivo è molto più piccolo di quello e il guadagno finale sarà diluito di qualche percento.

String.intern () estrarre la stringa da Heap e inserirla in PermGen

Le stringhe interiorizzate vengono inserite in un'area di archiviazione diversa: Generazione permanente che è un'area della JVM riservata agli oggetti non utente, come Classi, Metodi e altri oggetti JVM interni. La dimensione di quest'area è limitata ed è molto preziosa del mucchio. Essendo questa area più piccola di Heap, ci sono più probabilità di utilizzare tutto lo spazio e ottenere una OutOfMemoryException.

String.intern () string sono garbage collection

Nelle nuove versioni di JVM anche le stringhe internalizzate sono garbage collection quando non sono referenziate da nessun oggetto.

Tenendo presente i 3 punti precedenti, si potrebbe dedurre che String intern () potrebbe essere utile solo in poche situazioni quando si fa un sacco di confronto tra stringhe, tuttavia è meglio non usare la stringa interna se non si sa esattamente cosa si sta facendo ...



1
Solo per aggiungere, a volte le eccezioni della memoria dell'heap possono essere recuperate, specialmente nei modelli threaded come le applicazioni web. Quando il permgen è esaurito, un'applicazione sarà in genere permanentemente non funzionante e spesso avrà un thrash delle risorse fino a quando non viene uccisa.
Taylor,

7

Quando dovrei usare questa funzione a favore di String.equals ()

Dato che fanno cose diverse, probabilmente mai.

Il interning delle stringhe per motivi di prestazioni, in modo da poterle confrontare per l'uguaglianza di riferimento, sarà utile solo se per qualche istante si mantengono riferimenti alle stringhe: le stringhe provenienti dall'input dell'utente o IO non verranno internate.

Ciò significa che nella tua applicazione ricevi input da una fonte esterna e lo elabori in un oggetto che ha un valore semantico - dice un identificatore - ma quell'oggetto ha un tipo indistinguibile dai dati grezzi e ha regole diverse su come il programmatore dovrebbe usalo.

È quasi sempre meglio creare un UserIdtipo che è internato (è facile creare un meccanismo di interning generico thread-safe) e si comporta come un enum aperto, piuttosto che sovraccaricare il java.lang.Stringtipo con la semantica di riferimento se si tratta di un ID utente.

In questo modo non si confonde il fatto che una particolare stringa sia stata internata o meno e si può incapsulare qualsiasi comportamento aggiuntivo richiesto nell'enum aperto.


6

Non sono a conoscenza di alcun vantaggio, e se ci fosse in uno penserebbe che equals () stesso userebbe intern () internamente (cosa che non fa).

Busting miti stagisti ()


7
Nonostante tu dica che non sei a conoscenza di alcun vantaggio, il tuo link pubblicato identifica il confronto via == come 5 volte più veloce e quindi importante per il codice performante incentrato sul testo
Brian Agnew,

3
Quando hai un sacco di paragone di testi da fare, finirai per esaurire lo spazio PermGen. Quando non c'è molto confronto del testo da fare, la differenza di velocità non ha importanza. Ad ogni modo, semplicemente non intern () le tue stringhe. Non ne vale la pena.
Bombe,

Prosegue anche affermando che il guadagno relativo complessivo sarà generalmente piccolo.
oggetti il

Non penso che quel tipo di logica sia valida. Buon collegamento però!
Daniel Rikowski,

1
@DR: quale logica? Questo è un grande errore. @objects: scusa ma i tuoi argomenti non sono all'altezza delle ragioni. Ci sono molto buone ragioni per utilizzare intern, e molto buone ragioni per cui equalsnon lo fa di default. Il link che hai pubblicato è bollock completo. L'ultimo paragrafo ammette anche che internha uno scenario di utilizzo valido: elaborazione di testi pesanti (ad esempio un parser). Concludere che "[XYZ] è pericoloso se non sai cosa stai facendo" è così banale che fa male fisicamente.
Konrad Rudolph,

4

Daniel Brückner ha assolutamente ragione. Lo string interning ha lo scopo di risparmiare memoria (heap). Il nostro sistema attualmente ha una hashmap gigante per contenere determinati dati. Man mano che il sistema si ridimensiona, l'hashmap sarà abbastanza grande da rendere l'heap di memoria (come abbiamo testato). Internando tutte le stringhe duplicate tutti gli oggetti nella hashmap, ci consente di risparmiare una notevole quantità di spazio heap.

Sempre in Java 7, le stringhe internate non vivono più in PermGen ma si accumulano invece. Quindi non devi preoccuparti delle sue dimensioni e sì, viene raccolta della spazzatura:

In JDK 7, le stringhe internate non sono più allocate nella generazione permanente dell'heap Java, ma sono invece allocate nella parte principale dell'heap Java (nota come generazione giovane e vecchia), insieme agli altri oggetti creati dall'applicazione . Questa modifica comporterà un maggior numero di dati che risiedono nell'heap Java principale e meno dati nella generazione permanente e, pertanto, potrebbe essere necessario regolare le dimensioni dell'heap. La maggior parte delle applicazioni vedrà solo differenze relativamente piccole nell'uso dell'heap a causa di questa modifica, ma le applicazioni più grandi che caricano molte classi o fanno un uso pesante del metodo String.intern () vedranno differenze più significative.


Devo ripeterlo: sul mio software, un dump dell'heap ha mostrato che la maggior parte dello spazio dell'heap è stato utilizzato dalle Stringistanze. Quando ho visto il loro contenuto, ho visto molti duplicati e ho deciso di passare a intern(), che ha salvato centinaia di MB.
Matthieu,

4

Ci sono effetti collaterali non menzionati nel Javadoc, ovvero più o meno ottimizzazione da parte del compilatore JIT?

Non conosco il livello JIT, ma esiste un supporto bytecode diretto per il pool di stringhe , che viene implementato magicamente ed efficientemente con una CONSTANT_String_infostruttura dedicata (a differenza della maggior parte degli altri oggetti che hanno rappresentazioni più generiche).

JVM

JVMS 7 5.1 dice :

Un letterale stringa è un riferimento a un'istanza della classe String ed è derivato da una struttura CONSTANT_String_info (§4.4.3) nella rappresentazione binaria di una classe o interfaccia. La struttura CONSTANT_String_info fornisce la sequenza di punti di codice Unicode che costituiscono la stringa letterale.

Il linguaggio di programmazione Java richiede che i letterali stringa identici (ovvero i letterali che contengono la stessa sequenza di punti di codice) debbano fare riferimento alla stessa istanza della classe String (JLS §3.10.5). Inoltre, se il metodo String.intern viene chiamato su qualsiasi stringa, il risultato è un riferimento alla stessa istanza della classe che verrebbe restituita se quella stringa apparisse come un valore letterale. Pertanto, la seguente espressione deve avere il valore vero:

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

Per derivare letteralmente una stringa, Java Virtual Machine esamina la sequenza di punti di codice forniti dalla struttura CONSTANT_String_info.

  • Se il metodo String.intern è stato precedentemente chiamato su un'istanza della classe String contenente una sequenza di punti di codice Unicode identici a quelli forniti dalla struttura CONSTANT_String_info, il risultato della derivazione letterale di stringa è un riferimento a quella stessa istanza della classe String.

  • Altrimenti, viene creata una nuova istanza della classe String contenente la sequenza di punti di codice Unicode forniti dalla struttura CONSTANT_String_info; un riferimento a quell'istanza di classe è il risultato della derivazione letterale di stringa. Infine, viene invocato il metodo intern della nuova istanza String.

bytecode

È anche istruttivo esaminare l'implementazione del bytecode su OpenJDK 7.

Se decompiliamo:

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

abbiamo nel pool costante:

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

e 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

Nota come:

  • 0e 3: ldc #2viene caricata la stessa costante (i letterali)
  • 12: viene creata una nuova istanza di stringa (con #2come argomento)
  • 35: ae cvengono confrontati come oggetti regolari conif_acmpne

La rappresentazione di stringhe costanti è piuttosto magica sul bytecode:

  • ha una struttura CONSTANT_String_info dedicata , a differenza degli oggetti normali (ad es. new String)
  • la struttura punta a una CONSTANT_Utf8_info struttura che contiene i dati. Questi sono gli unici dati necessari per rappresentare la stringa.

e la citazione JVMS sopra sembra dire che ogni volta che Utf8 indicato è lo stesso, vengono caricate istanze identiche ldc.

Ho fatto test simili per i campi e:

  • static final String s = "abc"punta alla tabella costante tramite l' attributo ConstantValue
  • i campi non finali non hanno quell'attributo, ma possono comunque essere inizializzati con ldc

Bonus : confrontalo con il pool Integer , che non ha il supporto del bytecode diretto (cioè nessun CONSTANT_String_infoanalogo).


2

Esaminerei intern e == - confronto invece di uguale solo nel caso in cui il confronto uguale sia un collo di bottiglia in più confronti di stringa. Questo è altamente improbabile che possa aiutare con un numero limitato di confronti, perché intern () non è gratuito. Dopo aver internato in modo aggressivo le stringhe, le chiamate a intern () diventano sempre più lente.


2

Una perdita di memoria può derivare dall'uso di subString()quando il risultato è piccolo rispetto alla stringa di origine e l'oggetto ha una lunga durata.

La soluzione normale è quella di utilizzare new String( s.subString(...))ma quando si dispone di una classe che memorizza il risultato di un potenziale / probabile subString(...)e non si ha alcun controllo sul chiamante, è possibile considerare di memorizzare l' intern()argomento String passato al costruttore. Ciò rilascia il potenziale buffer di grandi dimensioni.


Interessante, ma forse questo dipende dall'implementazione.
Akostadinov,

1
La potenziale perdita di memoria sopra menzionata non si verifica in Java 1.8 e 1.7.06 (e versioni successive), vedere Modifiche alla rappresentazione interna di String apportate in Java 1.7.0_06 .
eremmel,

che conferma che le microottimizzazioni devono essere applicate solo quando necessario dopo una profilazione delle prestazioni e / o della memoria. Grazie.
Akostadinov,

2

Lo string interning è utile nel caso in cui il equals()metodo venga invocato spesso perché il equals()metodo esegue un rapido controllo per vedere se gli oggetti sono uguali all'inizio del metodo.

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

Questo di solito si verifica quando si cerca attraverso un Collectionaltro codice che può anche eseguire controlli di uguaglianza di stringhe.

Tuttavia, c'è un costo per l'internato, ho eseguito un microbench di alcuni codici e ho scoperto che il processo di interning aumenta il tempo di esecuzione di un fattore 10.

Il posto migliore per eseguire l'internamento è in genere quando si leggono chiavi memorizzate al di fuori del codice poiché le stringhe nel codice vengono automaticamente internate. Ciò avviene normalmente nelle fasi di inizializzazione dell'applicazione al fine di prevenire la penalità per il primo utente.

Un altro punto in cui è possibile farlo è quando si elabora l'input dell'utente che potrebbe essere utilizzato per eseguire ricerche chiave. Questo di solito accade nel processore della tua richiesta, nota che le stringhe internate dovrebbero essere tramandate.

A parte questo, non ha molto senso fare interning nel resto del codice in quanto generalmente non darà alcun vantaggio.


1

Vorrei che non valesse la pena per la manutenzione.

Il più delle volte, non ci sarà bisogno e nessun vantaggio in termini di prestazioni, a meno che il tuo codice non lavori molto con le sottostringhe. Nel qual caso la classe String utilizzerà la stringa originale più un offset per risparmiare memoria. Se il tuo codice utilizza molte sottostringhe, sospetto che causerà solo l'esplosione dei requisiti di memoria.


1

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

afferma che String.equals()utilizza "=="per confrontare gli Stringoggetti prima, secondo

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

confronta le lunghezze delle stringhe e quindi i contenuti.

(A proposito, le stringhe del codice prodotto in un catalogo di vendita possono avere la stessa lunghezza - BIC0417 è un casco di sicurezza per ciclisti, TIG0003 è una tigre maschio adulta viva - probabilmente avrai bisogno di tutti i tipi di licenze per ordinarne una. forse è meglio ordinare un casco di sicurezza allo stesso tempo.)

Quindi sembra che tu tragga vantaggio dalla sostituzione delle tue stringhe con la loro intern()versione, ma ottieni sicurezza - e leggibilità e conformità standard - senza usare "==" per la equals()tua programmazione. E la maggior parte di ciò che dirò dipende dal fatto che sia vero, se è vero.

Ma String.equals()prova che gli hai passato una stringa e non qualche altro oggetto, prima di usarlo "=="? Non sono qualificato per dirlo, ma immagino di no, perché la stragrande maggioranza di tali equals()operazioni sarà String to String, quindi il test viene quasi sempre superato. In effetti, dare la priorità a "==" all'interno String.equals()implica la certezza che si sta spesso confrontando la stringa con lo stesso oggetto reale.

Spero che nessuno sia sorpreso che le seguenti righe producano un risultato "falso":

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

Ma se si ipassa i.toString()a nella seconda riga, ovviamente lo è true.

I luoghi in cui potresti sperare in un beneficio dal tirocinio includono Sete Map, ovviamente. Spero che le stringhe internate abbiano i loro hashcode nella cache ... Penso che sarebbe un requisito. E spero di non aver appena dato via un'idea che potrebbe farmi guadagnare un milione di dollari. :-)

Per quanto riguarda la memoria, è anche ovvio che questo è un limite importante se il volume di stringhe è grande o se si desidera che la memoria utilizzata dal codice del programma sia molto piccola. Se il tuo volume di -distinct- Strings è molto grande, potrebbe essere il momento di prendere in considerazione l'utilizzo di un codice di programma di database dedicato per gestirli e un server di database separato. Allo stesso modo, se riesci a migliorare un piccolo programma (che deve essere eseguito contemporaneamente in 10000 istanze) avendo esso non memorizza affatto le sue stringhe.

È inutile creare una nuova stringa e poi scartarla immediatamente per il suo intern()sostituto, ma non esiste un'alternativa chiara, tranne per mantenere la stringa duplicata. Quindi davvero il costo di esecuzione è di cercare la tua stringa nel pool interno e quindi consentire al garbage collector di smaltire l'originale. E se è una stringa letterale, allora viene già internata.

Mi chiedo se intern()possa essere abusato dal codice di programma dannoso per rilevare se alcuni riferimenti String e i loro oggetti esistono già nel intern()pool e quindi esistono altrove nella sessione Java, quando ciò non dovrebbe essere noto. Ma ciò sarebbe possibile solo quando il codice del programma viene già utilizzato in modo fidato, immagino. Tuttavia, è qualcosa da considerare sulle librerie di terze parti che includi nel tuo programma per memorizzare e ricordare i tuoi numeri PIN ATM!


0

La vera ragione per usare stagista non è la precedente. Puoi usarlo dopo aver ricevuto un errore di memoria insufficiente. Molte stringhe in un tipico programma sono String.substring () di altre stringhe grandi [pensate di togliere un nome utente da un file XML da 100K. L'implementazione java è che la sottostringa contiene un riferimento alla stringa originale e l'inizio + fine in quella stringa enorme. (Il pensiero dietro è un riutilizzo della stessa grande corda)

Dopo 1000 file di grandi dimensioni, da cui salvi solo 1000 nomi brevi, manterrai in memoria tutti i 1000 file! Soluzione: in questo scenario basta usare smallsubstring.intern ()


Perché non solo creare una nuova stringa dalla sottostringa se ne hai bisogno?
Thorbjørn Ravn Andersen,

0

Sto usando intern per risparmiare memoria, tengo in memoria una grande quantità di dati String e mi muovo per usare intern () risparmiando una grande quantità di memoria. Sfortunatamente, sebbene utilizzi molta meno memoria, la memoria che utilizza è memorizzata nella memoria di PermGen non in Heap ed è difficile spiegare ai clienti come aumentare l'allocazione di questo tipo di memoria.

Quindi esiste un'alternativa a intern () per ridurre il consumo di memoria, (i vantaggi == contro equivalenti alle prestazioni non sono un problema per me)


0

Ammettiamolo: lo scenario del caso d'uso principale è quando leggi un flusso di dati (attraverso un flusso di input o da un ResultSet JDBC) e c'è una miriade di piccole stringhe che si ripetono dappertutto.

Ecco un piccolo trucco che ti dà un certo controllo sul tipo di meccanismo che desideri utilizzare per interiorizzare stringhe e altri immutabili, e un'implementazione di esempio:

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

Lo uso spesso quando leggo campi da stream o da ResultSet. Nota: LRUCacheè una semplice cache basata su LinkedHashMap<K,V>. Chiama automaticamente il retrieve()metodo fornito dall'utente per tutti i mancati cache.

Il modo per usarlo è crearne uno LRUInternalizerprima della lettura (o delle letture), usarlo per interiorizzare Stringhe e altri piccoli oggetti immutabili, quindi liberarlo. Per esempio:

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

0

Lo sto usando per memorizzare nella cache il contenuto di circa 36000 codici che si collegano ai nomi associati. Ho internato le stringhe nella cache perché molti dei codici puntano alla stessa stringa.

Internando le stringhe nella mia cache, mi assicuro che i codici che puntano alla stessa stringa puntino effettivamente alla stessa memoria, risparmiando così spazio su RAM.

Se le stringhe internate fossero effettivamente raccolte di rifiuti, non funzionerebbe affatto per me. Ciò sostanzialmente negherebbe lo scopo di internare. Il mio non sarà spazzatura raccolto perché sto tenendo un riferimento a ogni stringa nella cache.


No, tutte le stringhe uguali internate che sono in memoria in un determinato momento, saranno comunque lo stesso oggetto. Sarà un oggetto diverso rispetto alla stringa uguale che era in memoria prima che fosse garbage collection. Ma questo non è un problema perché la vecchia stringa non è più lì.
bdruemen

0

Il costo per internare una stringa è molto più del tempo risparmiato in un singolo confronto di stringhe A.equals (B). Usalo (per motivi di prestazioni) solo quando usi ripetutamente le stesse variabili stringa invariate. Ad esempio, se si scorre regolarmente su un elenco stabile di stringhe per aggiornare alcune mappe codificate sullo stesso campo stringa, è possibile ottenere un buon salvataggio.

Suggerirei di utilizzare lo string interning per ottimizzare le prestazioni durante l'ottimizzazione di parti specifiche del codice.

Ricorda anche che String sono immutabili e non commettono lo sciocco errore

String a = SOME_RANDOM_VALUE
a.intern()

ricordati di fare

String a = SOME_RANDOM_VALUE.intern()

0

Se stai cercando un sostituto illimitato per String.intern, anche garbage collection, per me funziona bene.

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

Naturalmente, se puoi stimare approssimativamente quante stringhe diverse ci saranno, usa semplicemente String.intern () con -XX: StringTableSize = highEnoughValue .


SoftRef avrebbe più senso.
Vach

@vach Usando WeakReference (invece di SoftReference) la memoria viene liberata in precedenza, quindi altre allocazioni potrebbero andare più veloci. Dipende da cos'altro sta facendo l'applicazione, l'una o l'altra potrebbe avere un senso.
bdruemen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.