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 ..
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.
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 .
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.
Commençons par l'exemple le plus simple.
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) .
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.
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
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.
new String()
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 ...
Comme ci-dessus.
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);
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.
intern()
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:
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;
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.
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 .