Che cos'è il interning di stringhe Java?


234

Cos'è lo string interning in Java, quando dovrei usarlo e perché ?



2
se String a = new String("abc"); String b = new String("abc"); poia.intern() == b.intern()
Asanka Siriwardena

Esempio di interning di
Poriya,

Dipende String.intern()dal ClassLoadersignificato di diversi classloader che creano "diverse" Strings, causando diverse interns?
AlikElzin-kilaka,

1
@ AlikElzin-kilaka no, i classloader sono del tutto irrilevanti per il interning delle stringhe. La prossima volta che hai una domanda, ti preghiamo di aprire una nuova domanda invece di pubblicarla come commento a un'altra domanda.
Holger,

Risposte:


233

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

Fondamentalmente fare String.intern () su una serie di stringhe assicurerà che tutte le stringhe con lo stesso contenuto condividano la stessa memoria. Quindi, se si dispone di un elenco di nomi in cui "john" appare 1000 volte, internando si assicura che solo un "john" sia effettivamente allocato memoria.

Questo può essere utile per ridurre i requisiti di memoria del programma. Ma tieni presente che la cache è gestita da JVM nel pool di memoria permanente che di solito ha dimensioni limitate rispetto all'heap, quindi non dovresti usare intern se non hai troppi valori duplicati.


Maggiori informazioni sui vincoli di memoria dell'utilizzo di intern ()

Da un lato, è vero che è possibile rimuovere i duplicati String interiorizzandoli. Il problema è che le stringhe interiorizzate vanno alla 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 e di solito è molto più piccola dell'heap. Chiamare intern () su una stringa ha l'effetto di spostarlo dall'heap alla generazione permanente e rischi di rimanere senza spazio PermGen.

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


Da JDK 7 (intendo in HotSpot), qualcosa è cambiato.

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.

- Da Java SE 7 Funzionalità e miglioramenti

Aggiornamento: le stringhe internate sono memorizzate nell'heap principale da Java 7 in poi. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes


1
"Ma tieni presente che la cache è gestita da JVM nel pool di memoria permanente che di solito ha dimensioni limitate ......" Puoi spiegarlo? Non ho capito
saplingPro

2
le stringhe "internate" sono memorizzate in una regione di memoria speciale nella JVM. Questa area di memoria ha in genere una dimensione fissa e non fa parte del normale heap Java in cui sono archiviati altri dati. A causa delle dimensioni fisse, può accadere che questa regione di memoria permanente si riempia di tutte le stringhe, causando brutti problemi (le classi non possono essere caricate e altre cose).
violoncello

@cello quindi, è simile alla cache?
alberelloPro

8
@grassPro: Sì, è una specie di cache, quella fornita nativamente dalla JVM. Come nota, a causa della fusione di Sun / Oracle JVM e JRockit, gli ingegneri JVM cercano di sbarazzarsi della regione di memoria permanente in JDK 8 ( openjdk.java.net/jeps/122 ), quindi non ci sarà qualsiasi limitazione di dimensione in futuro.
violoncello

9
I programmatori dovrebbero anche essere consapevoli del fatto che l'internamento delle stringhe può avere implicazioni sulla sicurezza. Se hai testo sensibile come password come stringhe in memoria, potrebbe rimanere in memoria per molto tempo anche se gli oggetti stringa reali sono stati a lungo GC'd. Ciò può essere problematico se i cattivi in ​​qualche modo ottengono l'accesso a un dump della memoria. Questo problema esiste anche senza internamento (dal momento che GC non è deterministico per iniziare con ecc.), Ma lo rende un po 'peggio. È sempre una buona idea usare al char[]posto del Stringtesto sensibile e azzerarlo non appena non è più necessario.
chris,

71

Ci sono alcune domande "interviste orecchiabili", come ad esempio perché ottieni uguale! se esegui il codice sottostante.

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

Se vuoi confrontare le stringhe che dovresti usare equals(). Quanto sopra verrà stampato uguale perché testStringè già stato internato dal compilatore per te. Puoi internare le stringhe usando il metodo intern come mostrato nelle risposte precedenti ....


5
Il tuo esempio è complicato perché risulterà sulla stessa stampa anche se usi il equalsmetodo. Potresti voler aggiungere un new String()confronto per mostrare più chiaramente la distinzione.
giannis christofakis,

@giannischristofakis ma se usiamo la nuova stringa (), il == fallirebbe? Java interiorizza automaticamente anche le stringhe nuove?
Deepak Selvakumar

@giannischristofakis ovviamente se usi new String () fallirà su ==. ma la nuova stringa (...). intern () non fallirà su == perché intern restituirà la stessa stringa. Semplice presuppone che il compilatore stia facendo il nuovo String ().
Intern

42

JLS

JLS 7 3.10.5 lo definisce e fornisce un esempio pratico:

Inoltre, un letterale stringa si riferisce sempre alla stessa istanza della classe String. Questo perché i letterali di stringa - o, più in generale, stringhe che sono i valori di espressioni costanti (§15.28) - vengono "internati" in modo da condividere istanze univoche, usando il metodo String.intern.

Esempio 3.10.5-1. Letterali per archi

Il programma costituito dall'unità di compilazione (§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"; }

e l'unità di compilazione:

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

produce l'output:

true true true true false true

JVM

JVMS 7 5.1 afferma che il interning viene implementato magicamente ed efficientemente con una CONSTANT_String_infostruttura dedicata (a differenza della maggior parte degli altri oggetti che hanno rappresentazioni più generiche):

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

Decompiliamo alcuni bytecode di OpenJDK 7 per vedere lo interning in azione.

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

Conclusione : esiste un supporto bytecode diretto per il pool di stringhe e la rappresentazione della memoria è efficiente.

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


19

Aggiornamento per Java 8 o più . In Java 8, lo spazio PermGen (Permanent Generation) viene rimosso e sostituito da Meta Space. La memoria del pool di stringhe viene spostata nell'heap di JVM.

Rispetto a Java 7, la dimensione del pool di stringhe viene aumentata nell'heap. Pertanto, hai più spazio per le stringhe interiorizzate, ma hai meno memoria per l'intera applicazione.

Ancora una cosa, hai già saputo che quando si confrontano 2 (riferimenti di) oggetti in Java, " ==" viene utilizzato per confrontare il riferimento dell'oggetto, " equals" viene utilizzato per confrontare il contenuto dell'oggetto.

Controlliamo questo codice:

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

Risultato:

value1 == value2 ---> vero

value1 == value3 ---> falso

value1.equals(value3) ---> vero

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

Ecco perché dovresti usare ' equals' per confrontare 2 oggetti String. Ed è così che intern()è utile.


2

Lo string interning è una tecnica di ottimizzazione da parte del compilatore. Se in un'unità di compilazione sono presenti due valori letterali stringa identici, il codice generato garantisce che nell'assieme sia creato un solo oggetto stringa per tutta l'istanza di quel valore letterale (caratteri racchiusi tra virgolette doppie).

Vengo da C # background, quindi posso spiegare dando un esempio da quello:

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

output dei seguenti confronti:

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

Nota 1 : gli oggetti vengono confrontati per riferimento.

Nota2 : typeof (int) .Name viene valutato con il metodo reflection in modo che non venga valutato al momento della compilazione. Qui questi confronti vengono effettuati in fase di compilazione.

Analisi dei risultati: 1) vero perché contengono entrambi lo stesso valore letterale e quindi il codice generato avrà un solo oggetto che fa riferimento a "Int32". Vedi nota 1 .

2) true perché viene controllato il contenuto di entrambi i valori, che è lo stesso.

3) FALSO perché str2 e obj non hanno lo stesso valore letterale. Vedi nota 2 .


3
È più forte di così. Qualsiasi stringa letterale caricata dallo stesso classloader farà riferimento alla stessa stringa. Vedi le specifiche JLS e JVM.
Marchese di Lorne,

1
@ user207421 infatti, è persino irrilevante a quale classloader appartiene il letterale stringa.
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.

0

Dal libro Deshmukh del programmatore OCP Java SE 11 ho trovato la spiegazione più semplice per l'Internet che è stata la seguente: Poiché le stringhe sono oggetti e poiché tutti gli oggetti in Java sono sempre memorizzati solo nello spazio heap, tutte le stringhe vengono archiviate nello spazio heap. Tuttavia, Java mantiene le stringhe create senza utilizzare la nuova parola chiave in un'area speciale dello spazio heap, che si chiama "pool di stringhe". Java mantiene le stringhe create usando la nuova parola chiave nello spazio heap normale.

Lo scopo del pool di stringhe è di mantenere un set di stringhe univoche. Ogni volta che si crea una nuova stringa senza utilizzare la nuova parola chiave, Java verifica se la stessa stringa esiste già nel pool di stringhe. In tal caso, Java restituisce un riferimento allo stesso oggetto String e, in caso contrario, Java crea un nuovo oggetto String nel pool di stringhe e ne restituisce il riferimento. Quindi, ad esempio, se usi la stringa "ciao" due volte nel codice come mostrato di seguito, otterrai un riferimento alla stessa stringa. Possiamo effettivamente testare questa teoria confrontando due diverse variabili di riferimento usando l' operatore == come mostrato nel seguente codice:

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 

== l' operatore verifica semplicemente se due riferimenti puntano allo stesso oggetto o meno e restituisce true se lo fanno. Nel codice precedente, str2 ottiene il riferimento allo stesso oggetto String creato in precedenza. Tuttavia, str3 e str4 ottengono riferimenti a due oggetti String completamente diversi. Ecco perché str1 == str2 restituisce true ma str1 == str3 e str3 == str4 restituiscono false. In effetti, quando fai una nuova stringa ("ciao"); vengono creati due oggetti String anziché uno solo se questa è la prima volta che la stringa "ciao" viene utilizzata in qualsiasi punto del programma - uno nel pool di stringhe a causa dell'uso di una stringa tra virgolette e uno nello spazio heap normale a causa dell'uso di una nuova parola chiave.

Il pool di stringhe è il modo Java di salvare la memoria del programma evitando la creazione di più oggetti String contenenti lo stesso valore. È possibile ottenere una stringa dal pool di stringhe per una stringa creata utilizzando la nuova parola chiave utilizzando il metodo intern di String. Si chiama "interning" di oggetti stringa. Per esempio,

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
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.