¿Qué es? Internalisation des chaînes de caractères en Java, quand je devrais l'utiliser, et pourquoi ?
Réponses
Trop de publicités?http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern()
En fait, en faisant String.intern() sur une série de chaînes de caractères, on s'assure que toutes les chaînes de caractères ayant le même contenu partagent la même mémoire. Ainsi, si vous avez une liste de noms dans laquelle 'john' apparaît 1000 fois, en effectuant un internage, vous vous assurez qu'un seul 'john' se voit allouer de la mémoire.
Cela peut être utile pour réduire les besoins en mémoire de votre programme. Mais attention, le cache est maintenu par la JVM dans un pool de mémoire permanente dont la taille est généralement limitée par rapport au tas. Vous ne devriez donc pas utiliser le cache interne si vous n'avez pas trop de valeurs en double.
Plus d'informations sur les contraintes de mémoire liées à l'utilisation de intern()
D'un côté, il est vrai que vous pouvez supprimer les doublons de String en les internalisant. Le problème est que les chaînes de caractères internalisées vont dans la génération permanente, qui est une zone de la JVM qui est réservée pour les objets non-utilisateurs, comme les classes, les méthodes et autres objets internes de la JVM. internes. La taille de cette zone est limitée, et est généralement beaucoup plus petite que le tas. L'appel de la fonction intern() sur une chaîne de caractères a pour effet de la déplacer du tas vers le tas. la déplacer du tas vers la génération permanente, et vous risquez de manquer de de manquer d'espace PermGen.
-- De : http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html
Depuis le JDK 7 (je veux dire dans HotSpot), quelque chose a changé.
Dans le JDK 7, les chaînes internées ne sont plus allouées dans la génération permanente du tas Java, mais sont plutôt allouées dans la partie principale du tas Java (connue sous le nom de jeune et ancienne générations), avec les autres objets créés par l'application. Ce changement se traduit par une augmentation des données résidant dans le tas principal de Java et une diminution des données dans la génération permanente, ce qui peut nécessiter un ajustement de la taille du tas. La plupart des applications ne verront que des différences relativement faibles dans l'utilisation du tas en raison de ce changement, mais les applications plus importantes qui chargent de nombreuses classes ou qui font un usage intensif de la méthode String.intern() verront des différences plus significatives.
-- De Caractéristiques et améliorations de Java SE 7
Mise à jour : Les chaînes de caractères internes sont stockées dans le tas principal à partir de Java 7. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes
Il y a des questions "accrocheuses", comme par exemple, pourquoi vous obtenez égaux ! si vous exécutez le code ci-dessous.
String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");
Si vous voulez comparer des chaînes de caractères, vous devez utiliser equals()
. Ce qui précède s'imprimera de manière égale parce que le testString
est déjà interné pour vous par le compilateur. Vous pouvez internaliser les chaînes vous-même en utilisant la méthode intern comme indiqué dans les réponses précédentes.....
JLS
JLS 7 3.10.5 le définit et donne un exemple pratique :
De plus, une chaîne littérale 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 dit que l'internat est mis en œuvre de façon magique et efficace avec un dévoué CONSTANT_String_info
struct (contrairement à la plupart des autres objets qui ont des représentations plus génériques) :
Une chaîne littérale est une référence à une instance de la classe String, et est dérivée 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 littéraux de chaîne identiques (c'est-à-dire ceux 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
Décompilons un bytecode d'OpenJDK 7 pour voir l'internat en action.
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
y 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
y3
: le mêmeldc #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
yc
sont comparés comme des objets ordinaires avecif_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 lorsque 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).
Mise à jour pour Java 8 ou plus . Dans Java 8, l'espace PermGen (Permanent Generation) est supprimé et remplacé par le Meta Space. La mémoire du String pool est déplacée vers le heap de la JVM.
Par rapport à Java 7, la taille du pool de chaînes est augmentée dans le tas. Par conséquent, vous disposez de plus d'espace pour les chaînes internalisées, mais vous avez moins de mémoire pour l'ensemble de l'application.
Une dernière chose, vous savez déjà que lors de la comparaison de 2 (références d') objets en Java, ' ==
' est utilisé pour comparer la référence de l'objet, ' equals
est utilisé pour comparer le contenu d'un objet.
Vérifions ce code :
String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();
Résultat :
value1 == value2
---> vrai
value1 == value3
---> faux
value1.equals(value3)
---> vrai
value1 == value3.intern()
---> vrai
C'est pourquoi vous devez utiliser ' equals
' pour comparer 2 objets String. Et c'est ainsi que intern()
est utile.
Puisque les chaînes sont des objets et que tous les objets en Java sont toujours stockés uniquement dans l'espace du tas, toutes les chaînes sont stockées dans l'espace du tas. Toutefois, Java conserve les chaînes créées sans utiliser le nouveau mot-clé dans une zone spéciale de l'espace du tas, appelée "string pool". Java conserve les chaînes créées à l'aide du nouveau mot-clé dans l'espace du tas normal.
L'objectif du pool de chaînes est de maintenir un ensemble de chaînes uniques. Chaque fois que vous créez une nouvelle chaîne sans utiliser le mot-clé new, Java vérifie si la même chaîne existe déjà dans le pool de chaînes. Si c'est le cas, Java renvoie une référence au même objet String et si ce n'est pas le cas, Java crée un nouvel objet String dans le string pool et renvoie sa référence. Ainsi, par exemple, si vous utilisez la chaîne "hello" deux fois dans votre code comme indiqué ci-dessous, vous obtiendrez une référence à la même chaîne. Nous pouvons réellement tester cette théorie en comparant deux variables de référence différentes à l'aide de la fonction \== comme indiqué dans le code suivant :
String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); //prints true
String str3 = new String("hello");
String str4 = new String("hello");
System.out.println(str1 == str3); //prints false
System.out.println(str3 == str4); //prints false
\== L'opérateur vérifie simplement si deux références pointent vers le même objet ou non et renvoie vrai si c'est le cas. Dans le code ci-dessus, str2 récupère la référence au même objet String qui a été créé précédemment. Cependant, str3 y str4 obtenir des références à deux objets String totalement différents. C'est pourquoi str1 == str2 retourne vrai mais str1 == str3 y str3 \== str4 Retourner faux . En fait, lorsque vous faites nouveau String("hello") ; deux objets String sont créés au lieu d'un seul si c'est la première fois que la chaîne "hello" est utilisée dans le programme - un dans le pool de chaînes à cause de l'utilisation d'une chaîne entre guillemets, et un dans l'espace régulier du tas à cause de l'utilisation du mot-clé new.
La mise en commun des chaînes de caractères est le moyen utilisé par Java pour économiser la mémoire du programme en évitant la création de plusieurs objets String contenant la même valeur. Il est possible d'obtenir une chaîne du pool de chaînes pour une chaîne créée à l'aide du mot-clé new en utilisant la méthode intern de String. C'est ce qu'on appelle "l'internage" des objets string. Par exemple,
String str1 = "hello";
String str2 = new String("hello");
String str3 = str2.intern(); //get an interned string obj
System.out.println(str1 == str2); //prints false
System.out.println(str1 == str3); //prints true
- Réponses précédentes
- Plus de réponses