web-dev-qa-db-fra.com

Le String Literal Pool est-il une collection de références à l'objet String ou une collection d'objets

Je suis tout confus après avoir lu l'article sur le site javaranch par Corey McGlone, l'auteur de The SCJP Tip Line. nommé Strings, Literally and the SCJP Java 6 Programmer Guide by Kathy Sierra (co-fondateur de javaranch) and Bert Bates.

Je vais essayer de citer ce que M. Corey et Mme Kathy Sierra ont cité à propos du String Literal Pool.

1. Selon M. Corey McGlone:

  • String Literal Pool est une collection de références qui pointent vers les objets String.

  • String s = "Hello"; (Supposons qu'il n'y ait aucun objet sur le tas nommé "Hello"), créera un objet String "Hello" Sur le tas et placera une référence à cet objet dans le String Literal Pool ( Table constante)

  • String a = new String("Bye"); (Supposons qu'il n'y ait pas d'objet sur le tas nommé "Bye", l'opérateur new obligera la JVM à créer un objet sur le tas.

Maintenant, l'explication de l'opérateur "new" Pour la création d'une chaîne et sa référence sont un peu confuses dans cet article, donc je mets le code et l'explication de l'article lui-même tel qu'il est ci-dessous.

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

Dans ce cas, nous nous retrouvons en fait avec un comportement légèrement différent en raison du mot clé "new." Dans un tel cas, les références aux deux littéraux String sont toujours placées dans la table constante (le String Literal Pool), mais, lorsque vous arrivez au mot clé "new,", la JVM est obligée de créer un nouvel objet String au moment de l'exécution, plutôt que d'utiliser celui de la table des constantes.

Voici le schéma qui l'explique ..

enter image description here

Cela signifie-t-il que le String Literal Pool a également une référence à cet objet?

Voici le lien vers l'article de Corey McGlone

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

2. Selon Kathy Sierra et Bert Bates dans le livre SCJP:

  • Pour rendre Java plus efficace en mémoire, la JVM a mis de côté une zone spéciale de mémoire appelée "String constant pool", lorsque le compilateur rencontre un String Literal, il vérifie le pool pour voir si un identique La chaîne existe déjà ou non. Sinon, elle crée un nouvel objet littéral de chaîne.

  • String s = "abc"; // Crée un objet String et une variable de référence ....

    c'est bien, mais ensuite j'ai été confus par cette déclaration:

  • String s = new String("abc") // Crée deux objets et une variable de référence.

    Il est dit dans le livre que .... un nouvel objet String dans la mémoire normale (non-pool), et "s" y fera référence ... tandis qu'un "abc" littéral supplémentaire sera placé dans le pool.

    Les lignes ci-dessus dans le livre entrent en collision avec celle de l'article de Corey McGlone.

    • Si String Literal Pool est une collection de références à l'objet String tel que mentionné par Corey McGlone, alors pourquoi l'objet littéral "abc" sera-t-il placé dans le pool (comme mentionné dans le livre)?

    • Et où réside ce pool littéral de chaînes?

Veuillez effacer ce doute, même si cela n'a pas trop d'importance lors de l'écriture d'un code, mais est très important du point de vue de la gestion de la mémoire, et c'est la raison pour laquelle je veux effacer ce funda.

68
Kumar Vivek Mitra

Je pense que le principal point à comprendre ici est la distinction entre String Java object and its contents - char[] Under private value field . String est essentiellement un wrapper autour du tableau char[], l'encapsulant et le rendant impossible à modifier afin que le String puisse rester immuable. Le String se souvient des parties de ce tableau qui sont réellement utilisées (voir ci-dessous). Cela signifie que vous pouvez avoir deux objets String différents (assez légers) pointant vers le même char[].

Je vais vous montrer quelques exemples, ainsi que hashCode() de chaque String et hashCode() du champ interne char[] value (Je l'appellerai texte pour le distinguer de la chaîne). Enfin, je vais afficher la sortie de javap -c -verbose, Ainsi qu'un pool constant pour ma classe de test. Veuillez ne pas confondre le pool constant de classe avec le pool littéral de chaînes. Ce ne sont pas tout à fait les mêmes. Voir aussi Comprendre la sortie de javap pour le pool constant .

Prérequis

Dans le but de tester, j'ai créé une telle méthode utilitaire qui rompt l'encapsulation String:

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

Il affichera hashCode() de char[] value, Nous aidant ainsi à comprendre si ce String particulier pointe vers le même texte char[] Ou non.

Deux littéraux de chaîne dans une classe

Commençons par l'exemple le plus simple.

Code Java

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

BTW si vous écrivez simplement "ab" + "c", Java effectuera la concaténation au moment de la compilation et le code généré sera exactement le même. Cela ne fonctionne que si toutes les chaînes sont connues au moment de la compilation) .

Pool constant de classe

Chaque classe a sa propre pool constant - une liste de valeurs constantes qui peuvent être réutilisées si elles se produisent plusieurs fois dans le code source. Il comprend des chaînes communes, des nombres, des noms de méthode, etc.

Voici le contenu du pool constant dans notre exemple ci-dessus.

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

La chose importante à noter est la distinction entre String objet constant (#2) Et le texte codé Unicode "abc" (#38) Vers lequel pointe la chaîne.

Code octet

Voici le code d'octet généré. Notez que les références one et two sont affectées avec la même constante #2 Pointant vers la chaîne "abc":

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

Production

Pour chaque exemple, j'imprime les valeurs suivantes:

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

Pas étonnant que les deux paires soient égales:

23583040
23583040
8918249
8918249

Ce qui signifie que non seulement les deux objets pointent vers le même char[] (Le même texte en dessous) donc equals() test passera. Mais encore plus, one et two sont exactement les mêmes références! Donc one == two Est également vrai. Évidemment, si one et two pointent vers le même objet, alors one.value Et two.value Doivent être égaux.

Littéral et new String()

Code Java

Maintenant, l'exemple que nous attendions tous - un littéral de chaîne et un nouveau String utilisant le même littéral. Comment cela fonctionnera-t-il?

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

Le fait que la constante "abc" Soit utilisée deux fois dans le code source devrait vous donner un indice ...

Pool constant de classe

Comme ci-dessus.

Code octet

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

new #3; //class Java/lang/String
dup
ldc #2; //String abc
invokespecial   #4; //Method Java/lang/String."<init>":(Ljava/lang/String;)V
astore_2    //two

Regarde attentivement! Le premier objet est créé de la même manière que ci-dessus, sans surprise. Il suffit d'une référence constante à String (#2) Déjà créé à partir du pool constant. Cependant, le deuxième objet est créé via un appel de constructeur normal. Mais! Le premier String est passé en argument. Cela peut être décompilé pour:

String two = new String(one);

Production

La sortie est un peu surprenante. La deuxième paire, représentant les références à l'objet String est compréhensible - nous avons créé deux objets String - un a été créé pour nous dans le pool constant et le second a été créé manuellement pour two. Mais pourquoi, sur terre, la première paire suggère que les deux objets String pointent vers le même tableau char[] value?!

41771
41771
8388097
16585653

Cela devient clair quand vous regardez comment String(String) constructeur fonctionne (grandement simplifié ici):

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

Voir? Lorsque vous créez un nouvel objet String basé sur un objet existant, il réutilise char[] value. String sont immuables, il n'est pas nécessaire de copier la structure de données connue pour ne jamais être modifiée.

Je pense que c'est l'indice de votre problème: même si vous avez deux objets String, ils pourraient toujours pointer vers le même contenu. Et comme vous pouvez le voir, l'objet String lui-même est assez petit.

Modification du runtime et intern()

Code Java

Disons que vous avez initialement utilisé deux chaînes différentes, mais après quelques modifications, elles sont toutes identiques:

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

Le Java (du moins le mien) n'est pas assez intelligent pour effectuer une telle opération au moment de la compilation, jetez un œil:

Pool constant de classe

Soudain, nous nous sommes retrouvés avec deux chaînes constantes pointant vers deux textes constants différents:

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

Code octet

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 première chaîne est construite comme d'habitude. Le second est créé en chargeant d'abord la chaîne constante "?abc" Puis en appelant substring(1) dessus.

Production

Pas de surprise ici - nous avons deux chaînes différentes, pointant vers deux textes char[] Différents en mémoire:

27379847
7615385
8388097
16585653

Eh bien, les textes ne sont pas vraiment différents, la méthode equals() donnera toujours true. Nous avons deux copies inutiles du même texte.

Maintenant, nous devons exécuter deux exercices. Tout d'abord, essayez d'exécuter:

two = two.intern();

avant d'imprimer des codes de hachage. Non seulement one et two pointent vers le même texte, mais ils sont la même référence!

11108810
11108810
15184449
15184449

Cela signifie que les tests one.equals(two) et one == two Réussiront. Nous avons également économisé de la mémoire car le texte "abc" N'apparaît qu'une seule fois dans la mémoire (la deuxième copie sera récupérée).

Le deuxième exercice est légèrement différent, vérifiez ceci:

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

Évidemment, one et two sont deux objets différents, pointant vers deux textes différents. Mais comment se fait-il que la sortie suggère qu'ils pointent tous les deux vers le même tableau char[]?!?

23583040
23583040
11108810
8918249

Je vous laisse la réponse. Il vous apprendra comment substring() fonctionne, quels sont les avantages d'une telle approche et quand elle peut conduire à de gros problèmes .

106
Tomasz Nurkiewicz