74 votes

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 et la SCJP Java 6 Programmer Guide par Kathy Sierra (co-fondatrice de javaranch) et Bert Bates.

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

1. Selon M. Corey McGlone :

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

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

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

Maintenant, l'explication de "new" pour la création d'une chaîne de caractères et de sa référence est un peu confus dans cet article, donc je mets le code et la référence de la chaîne de caractères dans l'article. l'article lui-même tels quels 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 obtenons en fait un comportement légèrement différent en raison du mot-clé "new." Dans ce cas, les références aux deux littéraux String sont toujours placées dans la table des constantes (le String Literal Pool), mais, lorsque l'on arrive 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 veut-il dire que le String Literal Pool a aussi 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 termes de mémoire, la JVM a mis de côté une zone spéciale de mémoire appelée "String constant pool". rencontre un String Literal, il vérifie le pool pour voir si une chaîne identique existe déjà ou non. Si ce n'est pas le cas, il crée un nouvel objet String Literal.

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

    c'est bien, mais alors 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 en

    Les lignes ci-dessus du livre entrent en collision avec celles de l'article de Corey McGlone.

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

    • Et où se trouve cette réserve de chaînes littérales ?

S'il vous plaît clarifier ce doute, bien qu'il ne sera pas trop important lors de l'écriture d'un code, mais est très important de l'aspect de la gestion de la mémoire, et c'est la raison pour laquelle je veux clarifier ce point.

1 votes

La manière dont le pool est géré peut, dans une certaine mesure, dépendre de l'implémentation de la JVM. Tant que quelque chose n'est pas corrigé par la directive spécification du langage ils sont libres d'expérimenter. Donc, que le pool contienne des références ou des objets pourrait bien dépendre, je crois.

0 votes

J'ai récemment rencontré exactement la même question avec les deux mêmes ressources que vous avez mentionnées. Je pense que par la déclaration l'objet "abc" sera placé dans le pool dans le livre, l'auteur signifie que la référence à l'objet "abc" sera stockée dans le pool. N'est-ce pas ? La réponse acceptée est assez informative mais je pense que c'est ce qui est demandé.

112voto

Tomasz Nurkiewicz Points 140462

Je pense que le point principal à comprendre ici est la distinction entre String Objet Java et son contenu - char[] en privé value champ . String est essentiellement une enveloppe autour de char[] en l'encapsulant et en le rendant impossible à modifier, de sorte que les String peuvent rester immuables. De même, le String La classe se souvient des parties de ce tableau qui sont effectivement utilisées (voir ci-dessous). Tout ceci signifie que vous pouvez avoir deux String objets (assez légers) pointant vers le même char[] .

Je vais vous montrer quelques exemples, ainsi que hashCode() de chaque String y hashCode() de l'interne char[] value (je l'appellerai texte pour la distinguer de la ficelle). Enfin, je montrerai javap -c -verbose ainsi qu'un pool de constantes pour ma classe de test. Ne confondez pas le pool de constantes de classe avec le pool de chaînes littérales. Ils ne sont pas tout à fait les mêmes. Voir aussi Comprendre la sortie de javap pour le pool constant .

Conditions préalables

Pour les besoins du test, j'ai créé une méthode utilitaire qui casse String encapsulation :

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

Il imprimera hashCode() de char[] value ce qui nous permet de comprendre si ce String pointe vers le même char[] texte ou non.

Deux chaînes littérales 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" Le compilateur 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 de caractères sont connues au moment de la compilation.

Pool constant de classe

Chaque classe a sa propre réservoir constant - une liste de valeurs constantes qui peuvent être réutilisées si elles apparaissent plusieurs fois dans le code source. Elle comprend des chaînes de caractères courantes, des nombres, des noms de méthodes, etc.

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

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

L'élément important à noter est la distinction entre String objet constant ( #2 ) et du texte codé en Unicode "abc" ( #38 ) vers laquelle la chaîne pointe.

Code octet

Voici le code d'octet généré. Notez que les deux one y two sont attribuées avec la même #2 constante pointant vers "abc" chaîne :

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

Sortie

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

Il n'est pas surprenant 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() le test sera réussi. Mais plus encore, one y two sont exactement les mêmes références ! Donc one == two est également vrai. De toute évidence, si one y two pointent vers le même objet, alors one.value y two.value doivent être égales.

Littéral et new String()

Code Java

Maintenant l'exemple que nous attendions tous - une chaîne littérale et une nouvelle String en utilisant le même littéral. Comment cela va-t-il fonctionner ?

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

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

Pool constant de classe

Idem que 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

Regardez bien ! Le premier objet est créé de la même manière que ci-dessus, sans surprise. Il prend juste une référence constante à l'objet déjà créé String ( #2 ) de la réserve constante. Cependant, le deuxième objet est créé par un appel normal au constructeur. Mais le premier String est passé comme argument. Ceci peut être décompilé en :

String two = new String(one);

Sortie

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

41771
41771
8388097
16585653

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

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

Tu vois ? Quand vous créez de nouvelles String basé sur un objet existant, il réutilisations char[] value . String sont immuables, il n'est pas nécessaire de copier une structure de données dont on sait qu'elle ne sera jamais modifiée.

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

Modification du temps d'exécution et intern()

Code Java

Disons que vous avez initialement utilisé deux cordes différentes mais qu'après quelques modifications, elles sont toutes identiques :

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

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

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. La seconde est créée en chargeant d'abord la constante "?abc" et ensuite appeler substring(1) sur elle.

Sortie

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

27379847
7615385
8388097
16585653

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

Maintenant, nous devrions faire deux exercices. D'abord, essayez de courir :

two = two.intern();

avant d'imprimer les codes de hachage. Non seulement les deux one y two pointent vers le même texte, mais il s'agit de la même référence !

11108810
11108810
15184449
15184449

Cela signifie que les deux one.equals(two) y one == two les tests seront réussis. Nous avons également économisé de la mémoire car "abc" Le texte n'apparaît qu'une seule fois en mémoire (la deuxième copie sera collectée par les ordures).

Le deuxième exercice est légèrement différent, regardez ça :

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

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

23583040
23583040
11108810
8918249

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

3 votes

+1 , merci d'avoir donné une connaissance approfondie de String. B

0 votes

@KumarVivekMitra : les détails exacts dépendent probablement de l'implémentation. En tout cas, les chaînes de caractères sont placés dans PermGen - mais s'il s'agit à peine de références à normal Le tas ou les tableaux de caractères eux-mêmes sont également dans PermGen - difficile à dire. Vérifiez également les références au bas de [Java Constant Pool : String ]( jimlife.wordpress.com/2007/08/10/java-constant-pool-string ) article.

0 votes

Une réponse excellente et approfondie, Tomasz ! C'est bien de voir des exemples en bytecode car cela révèle une partie de la magie qui se passe sous le capot. +1 camarade !

1voto

Jatin Patel Points 699
In Java, there is no operator overloading whatsoever, and that's why the comparison operators are only overloaded for the primitive types.

The 'String' class is not a primitive, thus it does not have an overloading for '==' and uses the default of comparing the address of the object in the computer's memory.

I'm not sure, but I think that in Java 7 or 8 oracle made an exception in the compiler to recognize str1 == str2 as str1.equals(str2)

0voto

Stephen C Points 255558

Je suis retourné lire l'article de CodeRanch, et je ne pense pas que votre résumé de ce qu'il dit soit exact. Par exemple, vous avez omis le point crucial que l'objet String pour "Hello" est allouée au moment du chargement de la classe, et non lors de l'exécution de l'instruction. Je soupçonne que votre résumé du texte SCJP est tout aussi inexact ... et que vos "doutes" sont (en grande partie) dus à votre lecture/résumé inexact.

Malheureusement, comme nous ne pouvons nous appuyer que sur votre résumé dans l'affaire SCJP, il est difficile de répondre à votre question de savoir quel texte est correct.

Je m'attends à ce que la réponse soit que les deux ont raison, et qu'ils disent à peu près la même chose de manière différente.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X