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