Qu'est-ce que Java String Interning?

234

Qu'est-ce que String Interning en Java, quand dois-je l'utiliser et pourquoi ?

saplingPro
la source
2
si String a = new String("abc"); String b = new String("abc"); alorsa.intern() == b.intern()
Asanka Siriwardena
Exemple d' internalisation de
chaîne de
Est-ce que cela String.intern()dépend ClassLoader, ce qui signifie, Est-ce que différents chargeurs de classe créent des "différents" String, provoquant des interns différents ?
AlikElzin-kilaka
1
@ AlikElzin-kilaka non, les chargeurs de classe sont totalement hors de propos pour l'internement de chaînes. La prochaine fois que vous aurez une question, veuillez ouvrir une nouvelle question au lieu de la poster en tant que commentaire à une autre question.
Holger

Réponses:

233

http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern ()

Fondamentalement, faire String.intern () sur une série de chaînes garantira que toutes les chaînes ayant le même contenu partagent la même mémoire. Donc, si vous avez une liste de noms où 'john' apparaît 1000 fois, en interne vous vous assurez qu'un seul 'john' est réellement alloué à la mémoire.

Cela peut être utile pour réduire les besoins en mémoire de votre programme. Mais sachez que le cache est maintenu par la machine virtuelle Java dans un pool de mémoire permanente qui est généralement de taille limitée par rapport au tas, vous ne devez donc pas utiliser interne si vous n'avez pas trop de valeurs en double.


En savoir plus sur les contraintes de mémoire liées à l'utilisation de intern ()

D'une part, il est vrai que vous pouvez supprimer les doublons de chaîne en les internalisant. Le problème est que les chaînes internalisées vont à la génération permanente, qui est une zone de la machine virtuelle Java qui est réservée aux objets non utilisateur, comme les classes, les méthodes et d'autres objets JVM internes. La taille de cette zone est limitée et est généralement beaucoup plus petite que le tas. L'appel intern () sur une chaîne a pour effet de la déplacer du tas vers la génération permanente, et vous risquez de manquer d'espace PermGen.

- De: http://www.codeinstructions.com/2009/01/busting-javalangstringintern-myths.html


Depuis JDK 7 (je veux dire dans HotSpot), quelque chose a changé.

Dans JDK 7, les chaînes internes 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 (appelées les générations jeunes et anciennes), ainsi que les autres objets créés par l'application. . Cette modification entraînera plus de données résidant dans le tas Java principal, et moins de données dans la génération permanente, et peut donc nécessiter des tailles de tas à ajuster. La plupart des applications ne verront que des différences relativement faibles dans l'utilisation du segment de mémoire en raison de ce changement, mais les applications plus grandes qui chargent de nombreuses classes ou utilisent fortement la méthode String.intern () verront des différences plus importantes.

- Depuis Java SE 7 Fonctionnalités et améliorations

Mise à jour: les chaînes internées sont stockées dans le tas principal à partir de Java 7. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes

Ashwinee K Jha
la source
1
"Mais sachez que le cache est maintenu par JVM dans un pool de mémoire permanent qui est généralement de taille limitée ......" Pouvez-vous expliquer cela? Je ne comprenais pas
saplingPro
2
les chaînes "internées" sont stockées dans une région mémoire spéciale de la JVM. Cette région de mémoire a généralement une taille fixe et ne fait pas partie du tas Java standard où d'autres données sont stockées. En raison de la taille fixe, il peut arriver que cette région de mémoire permanente soit remplie de toutes vos chaînes, ce qui entraîne de vilains problèmes (les classes ne peuvent pas être chargées et d'autres choses).
violoncelle
@cello donc, est-ce similaire à la mise en cache?
saplingPro
8
@grassPro: Oui, c'est une sorte de mise en cache, celle qui est fournie nativement par la JVM. À noter, en raison de la fusion de la JVM Sun / Oracle et de JRockit, les ingénieurs JVM tentent de se débarrasser de la région de mémoire permanente dans JDK 8 ( openjdk.java.net/jeps/122 ), donc il n'y aura pas toute limitation de taille à l'avenir.
violoncelle
9
Les programmeurs doivent également être conscients que l'internement de chaînes peut avoir des implications sur la sécurité. Si vous avez du texte sensible tel que des mots de passe en tant que chaînes en mémoire, il peut rester en mémoire très longtemps même si les objets chaîne réels ont longtemps été GC'd. Cela peut être gênant si des méchants ont accès à un vidage de mémoire. Ce problème existe même sans internement (car GC n'est pas déterministe pour commencer, etc.), mais il l'aggrave un peu. C'est toujours une bonne idée d'utiliser char[]au lieu de Stringpour le texte sensible et de le mettre à zéro dès qu'il n'est plus nécessaire.
chris
71

Il y a des questions "d'interview accrocheuses", comme pourquoi vous obtenez des égaux! si vous exécutez le morceau de code ci-dessous.

String s1 = "testString";
String s2 = "testString";
if(s1 == s2) System.out.println("equals!");

Si vous souhaitez comparer des chaînes, vous devez les utiliser equals(). Ce qui précède s'imprimera égal car le testStringest déjà interné pour vous par le compilateur. Vous pouvez interner les chaînes vous-même en utilisant la méthode interne comme indiqué dans les réponses précédentes ....

maslan
la source
5
Votre exemple est délicat car il entraînera la même impression même si vous utilisez la equalsméthode. Vous voudrez peut-être ajouter une new String()comparaison pour montrer plus clairement la distinction.
giannis christofakis
@giannischristofakis mais si nous utilisons de nouveaux String (), le == échouerait-il? Java intériorise-t-il également automatiquement les nouvelles chaînes?
Deepak Selvakumar
@giannischristofakis bien sûr, si vous utilisez un nouveau String () il échouera sur ==. mais la nouvelle chaîne (...). intern () n'échouera pas sur == car intern renverra la même chaîne. Simple compilateur fait supposer nouveau stagiaire String () dans littéraux.
MASLAN
42

JLS

JLS 7 3.10.5 le définit et donne un exemple pratique:

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

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

Le programme composé 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 que l'internement est implémenté de manière magique et efficace avec une CONSTANT_String_infostructure dédiée (contrairement à la plupart des autres objets qui ont des représentations plus génériques):

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 chaîne.

Le langage de programmation Java requiert que des littéraux de chaîne identiques (c'est-à-dire des littéraux qui contiennent la même séquence de points de code) doivent faire référence à la même instance de classe String (JLS §3.10.5). De plus, si la méthode String.intern est appelée sur n'importe quelle chaîne, le résultat est une référence à la même instance de classe qui serait retourné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 un littéral de chaîne, 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 déjà été appelée sur une instance de classe String contenant une séquence de points de code Unicode identique à celle donnée par la structure CONSTANT_String_info, le résultat de la dérivation littérale de chaîne est une référence à cette même instance de 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 d'une dérivation littérale de chaîne. Enfin, la méthode interne de la nouvelle instance de String est invoquée.

Bytecode

Décompilons du bytecode OpenJDK 7 pour voir l'internement 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);
    }
}

nous avons sur le bassin constant:

#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:

  • 0et 3: la même ldc #2constante est chargée (les littéraux)
  • 12: une nouvelle instance de chaîne est créée (avec #2comme argument)
  • 35: aet csont comparés comme des objets normaux avecif_acmpne

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

  • il a une structure CONSTANT_String_info dédiée , contrairement aux objets réguliers (par exemple new String)
  • la structure pointe vers une structure CONSTANT_Utf8_info qui contient les données. Ce sont les seules données nécessaires pour représenter la chaîne.

et la citation JVMS ci-dessus semble dire que chaque fois que l'Utf8 pointé est le même, 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 la table des constantes via l' attribut ConstantValue
  • les champs non finaux n'ont pas cet attribut, mais peuvent toujours être initialisés avec ldc

Conclusion : le pool de chaînes prend directement en charge le bytecode et la représentation en mémoire est efficace.

Bonus: comparez cela au pool Integer , qui n'a pas de prise en charge directe du bytecode (c'est-à-dire pas d' CONSTANT_String_infoanalogue).

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
19

Mise à jour pour Java 8 ou plus . Dans Java 8, l'espace PermGen (génération permanente) est supprimé et remplacé par Meta Space. La mémoire du pool de chaînes est déplacée vers le tas de 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 disposez de moins de mémoire pour l'ensemble de l'application.

Encore une chose, vous savez déjà que lors de la comparaison de 2 (références de) objets en Java, ' ==' est utilisé pour comparer la référence de l'objet, ' equals' est utilisé pour comparer le contenu de l'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 voilà comment intern()est utile.

nguyentt
la source
2

L'internement de chaînes est une technique d'optimisation du compilateur. Si vous avez deux littéraux de chaîne identiques dans une unité de compilation, le code généré garantit qu'il n'y a qu'un seul objet chaîne créé pour toute l'instance de ce littéral (caractères entre guillemets) dans l'assembly.

Je viens du milieu C #, donc je peux expliquer en donnant un exemple à partir de cela:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;

sortie des comparaisons suivantes:

Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true    
Console.WriteLine(obj == str2); // false !?

Remarque 1 : Les objets sont comparés par référence.

Remarque 2 : typeof (int) .Name est évalué par la méthode de réflexion afin qu'il ne soit pas évalué au moment de la compilation. Ici, ces comparaisons sont faites au moment de la compilation.

Analyse des résultats: 1) vrai car ils contiennent tous les deux le même littéral et donc le code généré n'aura qu'un seul objet référençant "Int32". Voir note 1 .

2) vrai parce que le contenu des deux valeurs est vérifié, ce qui est le même.

3) FAUX car str2 et obj n'ont pas le même littéral. Voir note 2 .

Robin Gupta
la source
3
C'est plus fort que ça. Tout littéral String chargé par le même chargeur de classe fera référence à la même chaîne. Voir les spécifications JLS et JVM.
Marquis de Lorne
1
@ user207421 en fait, il n'est même pas pertinent de savoir à quel chargeur de classe appartient le littéral de chaîne.
Holger
1
Java interning() method basically makes sure that if String object is present in SCP, If yes then it returns that object and if not then creates that objects in SCP and return its references

for eg: String s1=new String("abc");
        String s2="abc";
        String s3="abc";

s1==s2// false, because 1 object of s1 is stored in heap and other in scp(but this objects doesn't have explicit reference) and s2 in scp
s2==s3// true

now if we do intern on s1
s1=s1.intern() 

//JVM checks if there is any string in the pool with value “abc” is present? Since there is a string object in the pool with value “abc”, its reference is returned.
Notice that we are calling s1 = s1.intern(), so the s1 is now referring to the string pool object having value abc”.
At this point, all the three string objects are referring to the same object in the string pool. Hence s1==s2 is returning true now.
Rohan Kshirsagar
la source
0

À partir du livre Deshmukh du programmeur OCP Java SE 11, j'ai trouvé l'explication la plus simple pour l'internement qui s'est déroulée comme suit: puisque les chaînes sont des objets et que tous les objets en Java sont toujours stockés uniquement dans l'espace de tas, toutes les chaînes sont stockées dans l'espace de tas. Cependant, Java conserve les chaînes créées sans utiliser le nouveau mot clé dans une zone spéciale de l'espace de tas, appelée "pool de chaînes". Java conserve les chaînes créées à l'aide du nouveau mot clé dans l'espace de tas normal.

L'objectif du pool de chaînes est de conserver un ensemble de chaînes uniques. Chaque fois que vous créez une nouvelle chaîne sans utiliser le nouveau mot clé, Java vérifie si la même chaîne existe déjà dans le pool de chaînes. Si tel 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 pool de chaînes et renvoie sa référence. Ainsi, par exemple, si vous utilisez deux fois la chaîne "bonjour" 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 en utilisant l' opérateur == 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 obtient la référence au même objet String qui a été créé précédemment. Cependant, str3 et str4 obtiennent des références à deux objets String entièrement différents. C'est pourquoi str1 == str2 renvoie true mais str1 == str3 et str3 == str4 renvoie false. En fait, lorsque vous faites une nouvelle chaîne ("bonjour"); 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 n'importe où - un dans le pool de chaînes en raison de l'utilisation d'une chaîne entre guillemets et un dans l'espace de tas normal car de l'utilisation d'un nouveau mot-clé.

Le regroupement de chaînes est la façon dont Java économise 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 nouveau mot clé en utilisant la méthode intern de String. Il est appelé «internement» d'objets chaîne. 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
Hamza
la source