124 votes

Qu'est-ce que le pool de chaînes Java et en quoi "s" est-il différent de new String("s") ?

Qu'est-ce que l'on entend par String Pool ? Et quelle est la différence entre les déclarations suivantes :

String s = "hello";
String s = new String("hello");

Y a-t-il une différence entre le stockage de ces deux chaînes par la JVM ?

3 votes

1 votes

Sujet connexe : JEP 192 : Déduplication des chaînes de caractères en G1 : "Réduire l'ensemble des données vivantes du tas Java en améliorant le garbage collector G1 de sorte que les instances dupliquées de String soient automatiquement et continuellement dédupliquées."

161voto

Andrew Hare Points 159332

Le pool de chaînes de caractères est la mise en œuvre particulière de la JVM du concept de stage en entreprise :

En informatique, l'internalisation des chaînes de caractères est une méthode permettant de stocker une seule copie de chaque valeur de chaîne distincte, qui doit être immuable. L'internage des chaînes de caractères rend certaines tâches de traitement des chaînes plus efficaces en termes de temps ou d'espace, au au prix d'un temps supplémentaire lors de la chaîne est créée ou internée. Le site valeurs distinctes sont stockées dans un internée.

Fondamentalement, un pool de chaînes internes permet à un moteur d'exécution d'économiser de la mémoire en conservant des chaînes immuables dans un pool, de sorte que les zones de l'application peuvent réutiliser des instances de chaînes communes au lieu d'en créer de multiples.

Il est intéressant de noter que l'internat en chaîne est un exemple de l'approche de la modèle de conception poids plume :

Flyweight est un modèle de conception logiciel. Un flyweight est un objet qui minimise l'utilisation de la mémoire en partageant données que possible avec d'autres objets objets similaires ; c'est une façon d'utiliser d'utiliser des objets en grand nombre lorsqu'une simple représentation simple et répétée utiliserait une quantité inacceptable de mémoire.

17 votes

Excellente réponse, mais elle ne répond pas directement à la question. D'après votre description, il semble que l'exemple de code devrait référencer la même mémoire, n'est-ce pas ? Vous pouvez peut-être ajouter un simple résumé à votre réponse.

0 votes

C'est incorrect. L'exemple de code utilise le même littéral de chaîne interné dans les deux cas, mais la deuxième ligne crée un nouvel objet. Si cela vous aide à conceptualiser, pensez à la 1ère ligne comme : String s = GlobalStringObjectCache.get("hello");

8 votes

Copier-coller une réponse de Google qui ne répond même pas à la question ne devrait pas obtenir autant de votes positifs.

60voto

Michael Aaron Safyan Points 45071

Le pool de chaînes de caractères permet de réutiliser les constantes de chaînes de caractères, ce qui est possible car les chaînes de caractères en Java sont immuables. Si vous répétez la même constante de chaîne partout dans votre code Java, vous ne pouvez en fait avoir qu'une seule copie de cette chaîne dans votre système, ce qui est l'un des avantages de ce mécanisme.

Lorsque vous utilisez String s = "string constant"; vous obtenez la copie qui se trouve dans le pool de chaînes. Cependant, lorsque vous faites String s = new String("string constant"); vous forcez l'attribution d'une copie.

0 votes

Vous voulez dire que de cette façon il y a deux copies de "string constant" dans la mémoire ? Je sais que String s = "string constant" sera alloué dans le pool de string. String s = new String("string constant") va allouer la chaîne à ?

4 votes

Le deuxième fragment de code alloue une nouvelle référence au littéral existant dans le pool, et non une copie. Il n'y a qu'une seule copie du littéral en mémoire.

0 votes

"lorsque vous faites String s = new String("string constant") ; vous forcez l'allocation d'une copie", pourriez-vous l'expliquer plus en détail ? qu'est-ce que la "copie" ?

28voto

Ciro Santilli Points 3341

JLS

Comme indiqué par Andrew Le concept est appelé "internat" par le JLS.

Passage pertinent de JLS 7 3.10.5 :

De plus, un littéral de chaîne fait toujours référence à la même instance de la classe String. En effet, les chaînes littérales - ou, plus généralement, les chaînes qui sont les valeurs d'expressions constantes (§15.28) - sont "internées" de manière à partager des instances uniques, à l'aide de la méthode String.intern.

Exemple 3.10.5-1. Littéraux de chaîne de caractères

Le programme constitué de l'unité de compilation (§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"; }

et l'unité de compilation :

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

produit la sortie :

true true true true false true

JVMS

JVMS 7 5.1 dit :

Un littéral de chaîne est une référence à une instance de la classe String, et est dérivé d'une structure CONSTANT_String_info (§4.4.3) dans la représentation binaire d'une classe ou d'une interface. La structure CONSTANT_String_info donne la séquence de points de code Unicode constituant le littéral de chaîne.

Le langage de programmation Java exige que les chaînes littérales identiques (c'est-à-dire celles qui contiennent la même séquence de points de code) fassent référence à la même instance de la classe String (JLS §3.10.5). En outre, si la méthode String.intern est appelée sur une chaîne quelconque, le résultat est une référence à la même instance de classe qui serait renvoyée si cette chaîne apparaissait comme un littéral. Ainsi, l'expression suivante doit avoir la valeur true :

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

Pour dériver une chaîne littérale, la machine virtuelle Java examine la séquence de points de code donnée par la structure CONSTANT_String_info.

  • Si la méthode String.intern a été précédemment appelée sur une instance de la classe String contenant une séquence de points de code Unicode identique à celle donnée par la structure CONSTANT_String_info, alors le résultat de la dérivation des chaînes de caractères est une référence à cette même instance de la classe String.

  • Sinon, une nouvelle instance de la classe String est créée contenant la séquence de points de code Unicode donnée par la structure CONSTANT_String_info ; une référence à cette instance de classe est le résultat de la dérivation du littéral de chaîne. Enfin, la méthode intern de la nouvelle instance de String est invoquée.

Bytecode

Il est également instructif d'examiner l'implémentation du bytecode sur OpenJDK 7.

Si nous décompilons :

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

que nous avons sur la piscine constante :

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

et 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

Notez comment :

  • 0 et 3 : le même ldc #2 la constante est chargée (les littéraux)
  • 12 une nouvelle instance de chaîne est créée (avec #2 comme argument)
  • 35 : a et c sont comparés comme des objets ordinaires avec if_acmpne

La représentation des chaînes constantes est assez magique sur le bytecode :

  • il dispose d'une CONSTANT_String_info contrairement aux objets ordinaires (par exemple, les new String )
  • la structure pointe vers un Structure CONSTANT_Utf8_info qui contient les données. Ce sont les seules données nécessaires pour représenter la chaîne de caractères.

et la citation de JVMS ci-dessus semble dire que chaque fois que l'Utf8 pointé est le même, alors des instances identiques sont chargées par ldc .

J'ai fait des tests similaires pour les champs, et.. :

  • static final String s = "abc" pointe vers le tableau des constantes par le biais du Attribut ConstantValue
  • Les champs non finaux n'ont pas cet attribut, mais peuvent tout de même être initialisés à l'aide de la fonction ldc

Conclusion Il existe un support direct du bytecode pour le pool de chaînes de caractères, et la représentation mémoire est efficace.

Bonus : comparez cela à la Pool de nombres entiers qui n'a pas de support direct du bytecode (c'est-à-dire pas de CONSTANT_String_info analogique).

0 votes

2 objets différents : l'un est dans le pool de chaînes avec abc a deux références, c'est-à-dire a et b. L'autre dans le tas avec abc a une référence, c'est-à-dire c.

17voto

Chris Dennett Points 12396

Les objets string sont essentiellement des enveloppes autour des littéraux de chaîne. Les objets string uniques sont mis en commun pour éviter la création inutile d'objets, et la JVM peut décider de mettre en commun les littéraux string en interne. Il existe également un support direct du bytecode pour les constantes String qui sont référencées plusieurs fois, à condition que le compilateur le supporte.

Lorsque vous utilisez un littéral, par exemple String str = "abc"; l'objet du pool est utilisé. Si vous utilisez String str = new String("abc"); Dans le cas d'un objet de type "string literal", un nouvel objet est créé, mais la chaîne littérale existante peut être réutilisée au niveau de la JVM ou du bytecode (au moment de la compilation).

Vous pouvez le vérifier par vous-même en créant de nombreuses chaînes de caractères dans une boucle for et en utilisant la fonction == pour vérifier l'égalité des objets. Dans l'exemple suivant, string.value est privé de String et contient la chaîne littérale utilisée. Comme il est privé, on doit y accéder par réflexion.

public class InternTest {
    public static void main(String[] args) {
        String rehi = "rehi";
        String rehi2 = "rehi";
        String rehi2a = "not rehi";
        String rehi3 = new String("rehi");
        String rehi3a = new String("not rehi");
        String rehi4 = new String(rehi);
        String rehi5 = new String(rehi2);
        String rehi6 = new String(rehi2a);

        String[] arr  = new String[] { rehi, rehi2, rehi2a, rehi3, rehi3a, rehi4, rehi5, rehi6 };
        String[] arr2 = new String[] { "rehi", "rehi (2)", "not rehi", "new String(\"rehi\")", "new String(\"not rehi\")", "new String(rehi)", "new String(rehi (2))", "new String(not rehi)" };

        Field f;
        try {
            f = String.class.getDeclaredField("value");
            f.setAccessible(true);
        } catch (NoSuchFieldException | SecurityException e) {
            throw new IllegalStateException(e);
        }

        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                System.out.println("i: " +arr2[i]+", j: " +arr2[j]);
                System.out.println("i==j: " + (arr[i] == arr[j]));
                System.out.println("i equals j: " + (arr[i].equals(arr[j])));
                try {
                    System.out.println("i.value==j.value: " + (f.get(arr[i]) == f.get(arr[j])));
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
                System.out.println("========");
            }
        }
    }
}

Sortie :

i: rehi, j: rehi
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi, j: rehi (2)
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi, j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi, j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi, j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi, j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi, j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi, j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi (2), j: rehi
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: rehi (2)
i==j: true
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi (2), j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: rehi (2), j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: rehi (2), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: rehi (2)
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: not rehi
i==j: true
i equals j: true
i.value==j.value: true
========
i: not rehi, j: new String("rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: new String("not rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: not rehi, j: new String(rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: new String(rehi (2))
i==j: false
i equals j: false
i.value==j.value: false
========
i: not rehi, j: new String(not rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: rehi (2)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("rehi"), j: new String("rehi")
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("rehi"), j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("rehi"), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: rehi (2)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: not rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String("not rehi"), j: new String("rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: new String("not rehi")
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String("not rehi"), j: new String(rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: new String(rehi (2))
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String("not rehi"), j: new String(not rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: rehi (2)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi), j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi), j: new String(rehi)
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: new String(rehi (2))
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi (2)), j: rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: rehi (2)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: not rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi (2)), j: new String("rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: new String("not rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(rehi (2)), j: new String(rehi)
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: new String(rehi (2))
i==j: true
i equals j: true
i.value==j.value: true
========
i: new String(rehi (2)), j: new String(not rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: rehi
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: rehi (2)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: not rehi
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(not rehi), j: new String("rehi")
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: new String("not rehi")
i==j: false
i equals j: true
i.value==j.value: true
========
i: new String(not rehi), j: new String(rehi)
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: new String(rehi (2))
i==j: false
i equals j: false
i.value==j.value: false
========
i: new String(not rehi), j: new String(not rehi)
i==j: true
i equals j: true
i.value==j.value: true
========

0 votes

String s1 = new String("abc"), String s2 = new String("abc"). s1 != s2, c'est parce que les deux objets sont différents. Mais dans la mémoire il y a une copie de 'abc' ou deux ? où dose jvm alloue le 'abc' quand il est créé par le constructeur.

0 votes

Dans la plupart des cas (lorsque la taille de la chaîne et celle du tableau de caractères sous-jacent sont égales), le nouvel objet Chaîne aura le même tableau de caractères sous-jacent que l'objet Chaîne passé. Il n'y a donc qu'une seule copie de 'abc' en mémoire (représentée sous la forme d'un tableau de caractères), mais deux chaînes l'utilisent.

1 votes

Cette réponse est tout simplement fausse, donc les votes positifs devraient être supprimés. La construction new String("word") ne crée une nouvelle chaîne dans le pool que s'il n'y a pas de chaîne littérale dans le pool avec la même valeur. Il créera cependant un nouvel objet String qui fait référence à tout littéral existant dans le pool, d'où le résultat de la vérification de l'égalité des références d'objets.

7voto

killjoy Points 426

Il est curieux que personne n'ait répondu directement à la question alors que la plupart des réponses ont beaucoup de votes positifs.

En résumé, la première crée une entrée dans le String Pool, qui peut être réutilisée (plus efficace grâce aux liens ci-dessus sur l'immuabilité, fondamentalement, l'internage), et la seconde crée un nouvel objet String (plus coûteux).

Les deux objets vivent dans le Heap. Les références à ces deux objets seront dans la pile du thread.

http://www.journaldev.com/797/what-is-java-string-pool donne un aperçu clair de la manière dont cela est réalisé

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