web-dev-qa-db-fra.com

Qu'est-ce que Java String interning?

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

209
saplingPro

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

Fondamentalement, String.intern () sur une série de chaînes garantit 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 vous assurant que seul un "john" est réellement alloué à la mémoire.

Cela peut être utile pour réduire les besoins en mémoire de votre programme. Sachez toutefois que le cache est géré par la machine virtuelle Java dans un pool de mémoire permanente dont la taille est généralement inférieure à celle du segment de mémoire. Vous ne devez donc pas utiliser intern si vous ne possédez pas trop de valeurs en double.


Plus d'informations 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 réservée aux objets non utilisateurs, tels que les classes, les méthodes et les 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 le déplacer du tas à la génération permanente et vous risquez de manquer d'espace dans PermGen.

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


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

Dans JDK 7, les chaînes internées ne sont plus allouées dans la génération permanente du segment Java, mais dans la partie principale du segment Java (connues sous le nom de jeune et vieux). générations), ainsi que les autres objets créés par l’application. Cette modification se traduira par plus de données résidant dans le segment de mémoire principal Java, et moins de données dans la génération permanente, ce qui peut nécessiter l'ajustement des tailles de segment de mémoire. En raison de cette modification, la plupart des applications ne verront que des différences relativement faibles dans l'utilisation du segment de mémoire, mais les applications plus volumineuses qui chargent de nombreuses classes ou utilisent beaucoup la méthode String.intern () verront des différences plus importantes.

- De fonctionnalités et améliorations de Java SE 7

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

211
Ashwinee K Jha

Il y a quelques questions "d'interview accrocheuses", telles que pourquoi vous obtenez égal! si vous exécutez le code ci-dessous.

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

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

60
maslan

JLS

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

De plus, un littéral 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, à l'aide de la méthode String.intern.

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

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 indique que l'internat est implémenté de manière magique et efficace avec une structure CONSTANT_String_info dédiée (contrairement à la plupart des autres objets ayant 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 de chaîne.

Le langage de programmation Java requiert que les littéraux de chaîne identiques (c'est-à-dire contenant la même séquence de points de code) doivent faire référence à la même instance de la classe String (JLS §3.10.5). De plus, si la méthode String.intern est appelée sur une chaîne, le résultat est une référence à la même instance de classe qui serait renvoyée si cette chaîne apparaissait sous la forme d'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 la classe String contenant une séquence de points de code Unicode identique à celle fournie 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 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 d'une dérivation littérale de chaîne. Enfin, la méthode intern de la nouvelle instance String est appelée.

Bytecode

Décompilons quelques bytecodes OpenJDK 7 pour voir interner 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 pool 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:

  • 0 et 3: la même constante ldc #2 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 a une structure dédiée CONSTANT_String_info , contrairement aux objets ordinaires (par exemple, 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.

et la citation JVMS ci-dessus semble indiquer que chaque fois que le fichier Utf8 désigné est identique, 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 par l'intermédiaire de attribut ConstantValue
  • les champs non finaux n'ont pas cet attribut, mais peuvent toujours être initialisés avec ldc

Conclusion : il existe un support direct de bytecode pour le pool de chaînes, et la représentation en mémoire est efficace.

Bonus: comparez cela au Integer pool , qui ne prend pas directement en charge le bytecode (c'est-à-dire, aucun CONSTANT_String_info analogique).

Mise à jour pour Java 8 ou plus. Dans Java 8, l'espace PermGen (génération permanente) est supprimé et remplacé par le méta-espace. La mémoire du pool de chaînes est déplacée vers le segment de mémoire de la machine virtuelle Java.

Par rapport à Java 7, la taille du pool de chaînes est augmentée dans le segment de mémoire. Par conséquent, vous avez plus d'espace pour les chaînes internalisées, mais vous avez moins de mémoire pour toute l'application.

Encore une chose, vous savez déjà que lorsqu’on compare 2 (références de) objets en Java, on utilise '==' pour comparer la référence de l’objet, 'equals' pour comparer le contenu de objet.

Vérifions ce code:

String value1 = "70";
String value2 = "70";
String value3 = new Integer(70).toString();

Résultat:

value1 == value2 ---> true

value1 == value3 ---> false

value1.equals(value3) ---> true

value1 == value3.intern() ---> true

C'est pourquoi vous devriez utiliser 'equals' pour comparer 2 objets String. Et c’est comme ça que intern() est utile.

15
nguyentt

L'internalisation de chaînes est une technique d'optimisation par le 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 de chaîne créé pour toutes les occurrences de ce littéral (caractères entre guillemets) dans l'Assemblée.

Je viens de C #, donc je peux expliquer en donnant un exemple à partir de ça:

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 !?

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

Note2: 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) true car ils contiennent tous deux le même littéral et le code généré ne comporte donc qu'un seul objet référençant "Int32". voir note 1.

2) true car le contenu des deux valeurs est vérifié, lequel est identique.

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

2
Robin Gupta