String Literal Pool è una raccolta di riferimenti all’object String o una raccolta di oggetti

Sono completamente confuso dopo aver letto l’articolo sul sito di javaranch di Corey McGlone, l’autore di The SCJP Tip Line. chiamato Strings, Literally e SCJP Java 6 Programmer Guide di Kathy Sierra (co-founder of javaranch) and Bert Bates .

Proverò a citare ciò che Mr. Corey e Ms Kathy Sierra hanno citato su String Literal Pool.

1. Secondo il signor Corey McGlone:

Ora la spiegazione dell’operatore "new" per la creazione di una stringa e il suo riferimento è un po ‘confusa in questo articolo, quindi sto mettendo il codice e la spiegazione dall’articolo stesso così com’è-è sotto.

 public class ImmutableStrings { public static void main(String[] args) { String one = "someString"; String two = new String("someString"); System.out.println(one.equals(two)); System.out.println(one == two); } } 

In questo caso, abbiamo in realtà un comportamento leggermente diverso a causa della parola chiave "new." In tal caso, i riferimenti ai due valori letterali String sono ancora inseriti nella tabella costante (il pool letterale stringa), ma, quando si arriva alla parola chiave "new," la JVM è obbligata a creare un nuovo object String durante l’esecuzione. tempo, piuttosto che usare quello dal tavolo costante.

Ecco lo schema che lo spiega ..

inserisci la descrizione dell'immagine qui

Quindi vuol dire che anche String Literal Pool ha un riferimento a questo object?

Ecco il link all’articolo di Corey McGlone

http://www.javaranch.com/journal/200409/Journal200409.jsp#a1

2. Secondo Kathy Sierra e Bert Bates nel libro SCJP:

  • Per rendere la memoria Java più efficiente, la JVM mette da parte un’area speciale di memoria chiamata “String constant pool”, quando il compilatore incontra un String Literal, controlla il pool per vedere se una String identica già esce o meno. In caso contrario, crea un nuovo object letterale stringa.

  • String s = "abc"; // Crea un object String e una variabile di riferimento ….

    va bene, ma ora la seguente affermazione mi ha confuso.

  • String s = new String("abc") // Crea due oggetti e una variabile di riferimento.

    Dice nel libro che …. un nuovo object String nella memoria normale (non-pool), e “s” si riferirà ad esso … mentre un ulteriore letterale “abc” sarà inserito nella piscina.

    Le righe precedenti nel libro si scontrano con quella dell’articolo di Corey McGlone.

    • Se String Literal Pool è una raccolta di riferimenti all’object String come menzionato da Corey McGlone, allora come verrà inserito l’object letterale “abc” nel pool, come menzionato nel libro.

    • E dove risiede questo pool letterale di stringhe.

Si prega di chiarire questo dubbio, anche se non importa troppo durante la scrittura di un codice, ma è molto importante dall’aspetto della gestione della memoria, e questo è il motivo per cui voglio cancellare questo funda.

Penso che il punto principale da capire qui sia la distinzione tra l’object String Java e il suo contenuto – char[] sotto il campo del value privato . String è fondamentalmente un wrapper attorno all’array char[] , incapsulandolo e rendendo imansible la sua modifica in modo che la String possa rimanere immutabile. Anche la class String ricorda quali parti di questo array sono effettivamente usate (vedi sotto). Questo significa che puoi avere due diversi oggetti String (abbastanza leggeri) che puntano allo stesso char[] .

Vi mostrerò alcuni esempi, insieme a hashCode() di ogni String e hashCode() del campo di char[] value interno (lo chiamerò testo per distinguerlo dalla stringa). Infine mostrerò l’output di javap -c -verbose , insieme al pool costante per la mia class di test. Si prega di non confondere il pool costante della class con il pool letterale di stringhe. Non sono esattamente la stessa cosa. Vedi anche Capire l’output di javap per il Constant Pool .

Prerequisiti

Ai fini del test, ho creato un metodo di utilità simile che interrompe l’incapsulamento String :

 private int showInternalCharArrayHashCode(String s) { final Field value = String.class.getDeclaredField("value"); value.setAccessible(true); return value.get(s).hashCode(); } 

Stampa il valore hashCode() di char[] value , aiutandoci a capire se questa particolare String punta allo stesso testo char[] o meno.

Due stringhe letterali in una class

Iniziamo dall’esempio più semplice.

Codice Java

 String one = "abc"; String two = "abc"; 

Se si scrive semplicemente "ab" + "c" , il compilatore Java eseguirà la concatenazione in fase di compilazione e il codice generato sarà esattamente lo stesso. Funziona solo se tutte le stringhe sono conosciute al momento della compilazione.

Classe piscina costante

Ogni class ha un proprio pool costante : un elenco di valori costanti che possono essere riutilizzati se si verificano più volte nel codice sorgente. Include stringhe comuni, numeri, nomi dei metodi, ecc.

Ecco i contenuti del pool costante nel nostro esempio sopra.

 const #2 = String #38; // abc //... const #38 = Asciz abc; 

La cosa importante da notare è la distinzione tra String constant object ( #2 ) e Unicode con testo codificato "abc" ( #38 ) a cui punta la stringa.

Codice byte

Qui viene generato il codice byte. Si noti che sia one che two riferimenti sono assegnati con la stessa costante #2 punta alla stringa "abc" :

 ldc #2; //String abc astore_1 //one ldc #2; //String abc astore_2 //two 

Produzione

Per ogni esempio sto stampando i seguenti valori:

 System.out.println(showInternalCharArrayHashCode(one)); System.out.println(showInternalCharArrayHashCode(two)); System.out.println(System.identityHashCode(one)); System.out.println(System.identityHashCode(two)); 

Nessuna sorpresa che entrambe le coppie siano uguali:

 23583040 23583040 8918249 8918249 

Ciò significa che non solo entrambi gli oggetti puntano allo stesso char[] (lo stesso testo sotto) così equals() passerà il test. Ma ancora di più, one e two sono gli stessi identici riferimenti! Quindi one == two è vero. Ovviamente se one e two puntano allo stesso object, allora uno. one.value e two.value devono essere uguali.

new String() letterale e new String()

Codice Java

Ora l’esempio che tutti abbiamo aspettato – una stringa letterale e una nuova String usando lo stesso valore letterale. Come funzionerà?

 String one = "abc"; String two = new String("abc"); 

Il fatto che la costante "abc" venga usata due volte nel codice sorgente dovrebbe darti qualche suggerimento …

Classe piscina costante

Come sopra.

Codice byte

 ldc #2; //String abc astore_1 //one new #3; //class java/lang/String dup ldc #2; //String abc invokespecial #4; //Method java/lang/String."":(Ljava/lang/String;)V astore_2 //two 

Guarda attentamente! Il primo object è creato allo stesso modo di sopra, nessuna sorpresa. Richiede solo un riferimento costante alla String già creata ( #2 ) dal pool costante. Tuttavia, il secondo object viene creato tramite la normale chiamata del costruttore. Ma! La prima String viene passata come argomento. Questo può essere decompilato in:

 String two = new String(one); 

Produzione

L’output è un po ‘sorprendente. La seconda coppia, che rappresenta i riferimenti all’object String , è comprensibile: abbiamo creato due oggetti String : uno è stato creato per noi nel pool costante e il secondo è stato creato manualmente per two . Ma perché, sulla Terra, la prima coppia suggerisce che entrambi gli oggetti String puntano allo stesso array di char[] value ?!

 41771 41771 8388097 16585653 

Diventa chiaro quando si guarda come funziona il costruttore String(String) (molto semplificato qui):

 public String(String original) { this.offset = original.offset; this.count = original.count; this.value = original.value; } 

Vedere? Quando si crea un nuovo object String base a quello esistente, riutilizza il char[] value . String sono immutabili, non è necessario copiare la struttura dei dati che è nota per non essere mai modificata.

Penso che questo sia l’indizio del tuo problema: anche se hai due oggetti String , potrebbero comunque puntare allo stesso contenuto. E come puoi vedere, lo stesso object String è piuttosto piccolo.

Modifica runtime e intern()

Codice Java

Diciamo che inizialmente hai usato due stringhe diverse ma dopo alcune modifiche sono tutte uguali:

 String one = "abc"; String two = "?abc".substring(1); //also two = "abc" 

Il compilatore Java (almeno il mio) non è abbastanza intelligente per eseguire tale operazione in fase di compilazione, date un’occhiata:

Classe piscina costante

All’improvviso ci siamo ritrovati con due stringhe costanti che puntavano a due diversi testi costanti:

 const #2 = String #44; // abc const #3 = String #45; // ?abc const #44 = Asciz abc; const #45 = Asciz ?abc; 

Codice byte

 ldc #2; //String abc astore_1 //one ldc #3; //String ?abc iconst_1 invokevirtual #4; //Method String.substring:(I)Ljava/lang/String; astore_2 //two 

La stringa del pugno è costruita come al solito. Il secondo viene creato caricando dapprima la costante "?abc" e quindi chiamando la substring(1) su di essa.

Produzione

Nessuna sorpresa qui: abbiamo due stringhe diverse, che indicano due diversi testi char[] in memoria:

 27379847 7615385 8388097 16585653 

Bene, i testi non sono molto diversi , il metodo equals() produrrà ancora true . Abbiamo due copie non necessarie dello stesso testo.

Ora dovremmo eseguire due esercizi. Innanzitutto, prova a eseguire:

 two = two.intern(); 

prima di stampare codici hash. Non solo one e two indicano lo stesso testo, ma sono lo stesso riferimento!

 11108810 11108810 15184449 15184449 

Ciò significa che one.equals(two) entrambi one.equals(two) e one == two test. Inoltre abbiamo salvato un po ‘di memoria perché il testo "abc" appare solo una volta in memoria (la seconda copia sarà raccolta dei dati inutili).

Il secondo esercizio è leggermente diverso, dai un’occhiata a questo:

 String one = "abc"; String two = "abc".substring(1); 

Ovviamente one e two sono due oggetti diversi, che indicano due diversi testi. Ma come mai l’output suggerisce che entrambi puntano allo stesso char[] array?!?

 23583040 23583040 11108810 8918249 

Lascerò la risposta a te. Ti insegnerà come funziona la substring() , quali sono i vantaggi di tale approccio e quando può portare a grossi problemi .