¿Qué es el internamiento de Java String?

¿Qué es el internamiento de cadenas en Java, cuándo debería usarlo y por qué?

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

Básicamente, hacer String.intern () en una serie de cadenas asegurará que todas las cadenas que tengan el mismo contenido compartan la misma memoria. Por lo tanto, si tiene una lista de nombres donde ‘john’ aparece 1000 veces, al internar se asegurará de que solo se asigna memoria a ‘john’.

Esto puede ser útil para reducir los requisitos de memoria de su progtwig. Pero tenga en cuenta que JVM mantiene el caché en el grupo de memoria permanente, que generalmente tiene un tamaño limitado en comparación con el montón, por lo que no debe utilizar el interno si no tiene demasiados valores duplicados.


Más sobre las restricciones de memoria de usar pasante ()

Por un lado, es cierto que puede eliminar los duplicados de cadena internalizándolos. El problema es que las cadenas internalizadas van a la Generación permanente, que es un área de la JVM que está reservada para objetos no usuarios, como Clases, Métodos y otros objetos de JVM internos. El tamaño de esta área es limitado y, por lo general, es mucho más pequeño que el montón. Llamar a un interno () en una cadena tiene el efecto de moverlo desde el montón a la generación permanente, y corre el riesgo de quedarse sin espacio PermGen.

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


Desde JDK 7 (me refiero a HotSpot), algo ha cambiado.

En JDK 7, las cadenas internas ya no se asignan en la generación permanente de la stack de Java, sino que se asignan en la parte principal de la stack de Java (conocidas como generaciones jóvenes y antiguas), junto con otros objetos creados por la aplicación . Este cambio dará como resultado más datos que residen en el montón principal de Java y menos datos en la generación permanente, y por lo tanto puede requerir que se ajusten los tamaños de almacenamiento dynamic. La mayoría de las aplicaciones solo verán diferencias relativamente pequeñas en el uso del montón debido a este cambio, pero las aplicaciones más grandes que cargan muchas clases o hacen un uso intensivo del método String.intern () verán diferencias más significativas.

– De las características y mejoras de Java SE 7

Actualización: las cadenas internas se almacenan en el montón principal desde Java 7 en adelante. http://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html#jdk7changes

Hay algunas preguntas de “entrevista pegadiza” por las que obtienes

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

Si debe comparar las cadenas, debe usar equals() . Lo anterior se imprimirá igual, porque el testString for You. Puede internar las cuerdas usted mismo usando el método interno como se muestra en las respuestas anteriores ….

JLS

JLS 7 3.10.5 lo define y ofrece un ejemplo práctico:

Además, un literal de cadena siempre se refiere a la misma instancia de clase String. Esto se debe a que los literales de cadena (o, más generalmente, las cadenas que son los valores de las expresiones constantes (§15.28)) son “internados” para compartir instancias únicas, utilizando el método String.intern.

Ejemplo 3.10.5-1. Literales de cuerda

El progtwig que consiste en la unidad de comstackción (§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"; } 

y la unidad de comstackción:

 package other; public class Other { public static String hello = "Hello"; } 

produce la salida:

 true true true true false true 

JVMS

JVMS 7 5.1 dice que el internamiento se implementa de forma mágica y eficiente con una estructura CONSTANT_String_info dedicada (a diferencia de la mayoría de los otros objetos que tienen representaciones más genéricas):

Un literal de cadena es una referencia a una instancia de clase String, y se deriva de una estructura CONSTANT_String_info (§4.4.3) en la representación binaria de una clase o interfaz. La estructura CONSTANT_String_info proporciona la secuencia de puntos de código Unicode que constituyen la cadena literal.

El lenguaje de progtwigción Java requiere que los literales de cadena idénticos (es decir, los literales que contienen la misma secuencia de puntos de código) se refieran a la misma instancia de la clase String (JLS §3.10.5). Además, si se llama al método String.intern en cualquier cadena, el resultado es una referencia a la misma instancia de clase que se devolvería si esa cadena apareciera como un literal. Por lo tanto, la siguiente expresión debe tener el valor verdadero:

 ("a" + "b" + "c").intern() == "abc" 

Para derivar un literal de cadena, la Máquina Virtual Java examina la secuencia de puntos de código dada por la estructura CONSTANT_String_info.

  • Si el método String.intern ha sido invocado previamente en una instancia de clase String que contiene una secuencia de puntos de código Unicode idénticos a los de la estructura CONSTANT_String_info, el resultado de la derivación literal de cadena es una referencia a esa misma instancia de clase String.

  • De lo contrario, se crea una nueva instancia de clase String que contiene la secuencia de puntos de código Unicode dada por la estructura CONSTANT_String_info; una referencia a esa instancia de clase es el resultado de la derivación literal de cadena. Finalmente, se invoca el método interno de la nueva instancia de String.

Bytecode

Vamos a descomstackr algunos códigos de bytes OpenJDK 7 para ver el interinato en acción.

Si descomstackmos:

 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); } } 

tenemos en el grupo 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."":(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 

Tenga en cuenta cómo:

  • 0 y 3 : se carga la misma constante ldc #2 (los literales)
  • 12 : se crea una nueva instancia de cadena (con #2 como argumento)
  • 35 : a y c se comparan como objetos regulares con if_acmpne

La representación de cadenas constantes es bastante mágica en el bytecode:

  • tiene una estructura CONSTANT_String_info dedicada, a diferencia de los objetos normales (por ejemplo, new String )
  • la estructura apunta a una estructura CONSTANT_Utf8_info que contiene los datos. Ese es el único dato necesario para representar la cadena.

y la cita de JVMS anterior parece decir que siempre que el Utf8 señalado es el mismo, entonces instancias idénticas son cargadas por ldc .

He hecho pruebas similares para los campos, y:

  • static final String s = "abc" apunta a la tabla de constantes a través del atributo ConstantValue
  • los campos no finales no tienen ese atributo, pero aún se pueden inicializar con ldc

Conclusión : existe una compatibilidad directa con bytecode para el grupo de cadenas, y la representación de la memoria es eficiente.

Bonificación: compare eso con el conjunto de enteros , que no tiene soporte directo de bytecode (es decir, ningún análogo CONSTANT_String_info ).

Actualización para Java 8 o más . En Java 8, el espacio PermGen (Generación permanente) se elimina y se reemplaza por Meta Space. La memoria del conjunto de cadenas se mueve al montón de JVM.

Comparado con Java 7, el tamaño del grupo de cadenas aumenta en el montón. Por lo tanto, tiene más espacio para cadenas internalizadas, pero tiene menos memoria para toda la aplicación.

Una cosa más, ya has sabido que cuando se comparan 2 (referencias de) objetos en Java, ‘ == ‘ se usa para comparar la referencia del objeto, ‘ equals ‘ se usa para comparar el contenido del objeto.

Revisemos este código:

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

Resultado:

value1 == value2 —> true

value1 == value3 —> false

value1.equals(value3) —> true

value1 == value3.intern() —> true

Es por eso que deberías usar ‘ equals ‘ para comparar 2 objetos String. Y así es como el intern() es útil.

Interno de cadenas es una técnica de optimización por el comstackdor. Si tiene dos literales de cadena idénticos en una unidad de comstackción, el código generado asegura que solo haya un objeto de cadena creado para toda la instancia de ese literal (caracteres entre comillas dobles) dentro del ensamblaje.

Soy de origen C #, así que puedo explicar dando un ejemplo de eso:

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

salida de las siguientes comparaciones:

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

Nota1 : los objetos se comparan por referencia.

Nota 2 : typeof (int) .Name se evalúa por el método de reflexión por lo que no se evalúa en tiempo de comstackción. Aquí estas comparaciones se realizan en tiempo de comstackción.

Análisis de los resultados: 1) verdadero porque ambos contienen el mismo literal, por lo que el código generado tendrá solo un objeto que haga referencia a “Int32”. Ver Nota 1 .

2) verdadero porque se comprueba el contenido de ambos, el cual es el mismo.

3) FALSO porque str2 y obj no tienen el mismo literal. Ver Nota 2 .