Es String Literal Pool una colección de referencias al objeto String, o una colección de objetos

Estoy completamente confundido después de leer el artículo en el sitio javaranch por Corey McGlone, el autor de The SCJP Tip Line. nombrada Strings, literalmente, y la guía SCJP Java 6 Programmer Guide por Kathy Sierra (co-founder of javaranch) and Bert Bates .

Trataré de citar lo que el Sr. Corey y la Sra. Kathy Sierra han citado sobre String Literal Pool.

1. Según el Sr. Corey McGlone:

  • String Literal Pool es una colección de referencias que apunta a los String Objects.

  • String s = "Hello"; (Suponga que no hay ningún objeto en el montón denominado “Hola”), creará un objeto de cadena "Hello" en el montón, y colocará una referencia a este objeto en el conjunto literal de cadenas (tabla de constantes)

  • String a = new String("Bye"); (Suponga que no hay ningún objeto en el Heap llamado “Bye”, el new operador obligará a la JVM a crear un objeto en el Heap.

Ahora la explicación del operador "new" para la creación de un String y su referencia es un poco confusa en este artículo, así que estoy poniendo el código y la explicación del artículo en sí, tal como está debajo.

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

En este caso, en realidad terminamos con un comportamiento ligeramente diferente debido a la palabra clave "new." En tal caso, las referencias a los dos literales de Cadena todavía se ponen en la tabla constante (el Conjunto Literal de Cadenas), pero, cuando se llega a la palabra clave "new," la JVM está obligada a crear un nuevo objeto Cadena en ejecución. tiempo, en lugar de usar el de la tabla constante.

Aquí está el diagtwig que lo explica …

enter image description here

Entonces, ¿significa que el conjunto literal de cadenas también tiene una referencia a este objeto?

Aquí está el enlace al artículo de Corey McGlone

http://www.javaranch.com/journal/200409/Journal200409.jsp#a1

2. Según Kathy Sierra y Bert Bates en el libro SCJP:

  • Para que Java sea más eficiente en cuanto a la memoria, la JVM aparta un área especial de memoria llamada “conjunto constante de cadenas”, cuando el comstackdor encuentra un Literal de cadenas, comprueba el grupo para ver si ya existe o no una cadena idéntica. Si no, entonces crea un nuevo objeto literal String.

  • String s = "abc"; // Crea un objeto String y una variable de referencia ….

    eso está bien, pero ahora la statement siguiente me confundió.

  • String s = new String("abc") // Crea dos objetos y una variable de referencia.

    Dice en el libro que …. un nuevo objeto String en la memoria normal (sin agrupamiento), y “s” se referirá a él … mientras que un adicional, el literal “abc” se colocará en el conjunto.

    Las líneas anteriores en el libro colisionan con la del artículo de Corey McGlone.

    • Si String Literal Pool es una colección de referencias al objeto String como lo mencionó Corey McGlone, entonces, ¿cómo es que el objeto literal “abc” se colocará en el conjunto, como se menciona en el libro?

    • Y donde reside este String Literal Pool.

Por favor, aclare esta duda, aunque no importará demasiado al escribir un código, pero es muy importante desde el punto de vista de la administración de la memoria, y esa es la razón por la que quiero aclarar este fundamento.

Creo que el punto principal para entender aquí es la distinción entre String objeto String Java y su contenido – char[] en el campo de value privado . String es básicamente un envoltorio alrededor de la matriz char[] , que lo encapsula y hace que sea imposible modificarlo para que la String permanezca inmutable. Además, la clase String recuerda qué partes de esta matriz se utilizan realmente (ver a continuación). Esto significa que puede tener dos objetos String diferentes (bastante livianos) apuntando al mismo char[] .

Le mostraré algunos ejemplos, junto con hashCode() de cada String y hashCode() del campo de char[] value interno char[] value (lo llamaré texto para distinguirlo de la cadena). Finalmente, mostraré javap -c -verbose output, junto con el pool constante para mi clase de prueba. No confunda el grupo de constante de clases con el grupo de cadenas literales. No son lo mismo. Consulte también Comprender la salida de javap para el conjunto de constantes .

Requisitos previos

Con el propósito de probar, creé un método de utilidad que rompe String encapsulación de Cadenas:

 private int showInternalCharArrayHashCode(String s) { final Field value = String.class.getDeclaredField("value"); value.setAccessible(true); return value.get(s).hashCode(); } 

Imprimirá hashCode() del char[] value , lo que nos ayudará a entender si este String particular apunta al mismo texto char[] o no.

Dos literales de cadena en una clase

Comencemos por el ejemplo más simple.

Código Java

 String one = "abc"; String two = "abc"; 

Por cierto, si simplemente escribe "ab" + "c" , el comstackdor de Java realizará la concatenación en tiempo de comstackción y el código generado será exactamente el mismo. Esto solo funciona si todas las cadenas se conocen en tiempo de comstackción.

Grupo constante de clases

Cada clase tiene su propio conjunto constante : una lista de valores constantes que pueden reutilizarse si ocurren varias veces en el código fuente. Incluye cadenas comunes, números, nombres de métodos, etc.

Aquí están los contenidos del grupo constante en nuestro ejemplo anterior.

 const #2 = String #38; // abc //... const #38 = Asciz abc; 

Lo importante a tener en cuenta es la distinción entre String objeto constante String (n #2 ) y el texto codificado en Unicode "abc" (n #38 ) al que apunta el hilo.

Código de bytes

Aquí está el código de bytes generado. Tenga en cuenta que tanto one como two referencias se asignan con la misma constante #2 apuntando a la cadena "abc" :

 ldc #2; //String abc astore_1 //one ldc #2; //String abc astore_2 //two 

Salida

Para cada ejemplo estoy imprimiendo los siguientes valores:

 System.out.println(showInternalCharArrayHashCode(one)); System.out.println(showInternalCharArrayHashCode(two)); System.out.println(System.identityHashCode(one)); System.out.println(System.identityHashCode(two)); 

No es de sorprender que ambos pares sean iguales:

 23583040 23583040 8918249 8918249 

Lo que significa que no solo ambos objetos apuntan a la misma char[] (el mismo texto debajo) por lo que la prueba equals() pasará. ¡Pero aún más, one y two son exactamente las mismas referencias! Entonces one == two es verdad también. Obviamente, si one y two apuntan al mismo objeto, entonces one.value y two.value deben ser iguales.

Literal y new String()

Código Java

Ahora el ejemplo que todos esperamos: un string literal y un nuevo String usando el mismo literal. ¿Cómo funcionará esto?

 String one = "abc"; String two = new String("abc"); 

El hecho de que la constante "abc" se use dos veces en el código fuente debería darle alguna pista …

Grupo constante de clases

Lo mismo que arriba.

Código de bytes

 ldc #2; //String abc astore_1 //one new #3; //class java/lang/String dup ldc #2; //String abc invokespecial #4; //Method java/lang/String."":(Ljava/lang/String;)V astore_2 //two 

¡Mira cuidadosamente! El primer objeto se crea de la misma manera que arriba, no es sorpresa. Simplemente toma una referencia constante a String ( #2 ) ya creado del conjunto constante. Sin embargo, el segundo objeto se crea mediante una llamada de constructor normal. ¡Pero! La primera String se pasa como un argumento. Esto puede ser descomstackdo a:

 String two = new String(one); 

Salida

La salida es un poco sorprendente. El segundo par, que representa referencias al objeto String es comprensible: creamos dos objetos String , uno se creó para nosotros en el conjunto constante y el segundo se creó manualmente para two . Pero ¿por qué, en la Tierra, el primer par sugiere que ambos objetos String apuntan a la misma matriz de char[] value ?

 41771 41771 8388097 16585653 

Se vuelve claro cuando se observa cómo funciona el constructor String(String) (muy simplificado aquí):

 public String(String original) { this.offset = original.offset; this.count = original.count; this.value = original.value; } 

¿Ver? Cuando está creando un nuevo objeto String basado en uno existente, reutiliza el char[] value . String son inmutables, no es necesario copiar la estructura de datos que se sabe que nunca se modificará.

Creo que esta es la clave de su problema: incluso si tiene dos objetos String , aún podrían señalar los mismos contenidos. Y como pueden ver, el objeto String sí es bastante pequeño.

Modificación en tiempo de ejecución e intern()

Código Java

Digamos que inicialmente usaste dos cadenas diferentes pero después de algunas modificaciones son todas iguales:

 String one = "abc"; String two = "?abc".substring(1); //also two = "abc" 

El comstackdor de Java (al menos el mío) no es lo suficientemente inteligente como para realizar dicha operación en tiempo de comstackción, eche un vistazo:

Grupo constante de clases

De repente, terminamos con dos cadenas constantes que apuntan a dos textos constantes diferentes:

 const #2 = String #44; // abc const #3 = String #45; // ?abc const #44 = Asciz abc; const #45 = Asciz ?abc; 

Código de bytes

 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 cuerda del puño está construida como de costumbre. El segundo se crea cargando primero la cadena constante "?abc" y luego llamando a la substring(1) en ella.

Salida

No es de sorprender aquí, tenemos dos cadenas diferentes, apuntando a dos diferentes char[] textos en la memoria:

 27379847 7615385 8388097 16585653 

Bueno, los textos no son realmente diferentes , el método equals() seguirá siendo true . Tenemos dos copias innecesarias del mismo texto.

Ahora deberíamos ejecutar dos ejercicios. Primero, intente ejecutar:

 two = two.intern(); 

antes de imprimir códigos hash. ¡No solo one y two apuntan al mismo texto, sino que son la misma referencia!

 11108810 11108810 15184449 15184449 

Esto significa que one.equals(two) las one.equals(two) y one == two . También guardamos algo de memoria porque el texto "abc" aparece solo una vez en la memoria (la segunda copia será basura).

El segundo ejercicio es ligeramente diferente, mira esto:

 String one = "abc"; String two = "abc".substring(1); 

Obviamente, one y two son dos objetos diferentes, que apuntan a dos textos diferentes. ¿Pero cómo es que el resultado sugiere que ambos apuntan a la misma matriz de caracteres char[] ?

 23583040 23583040 11108810 8918249 

Dejaré la respuesta para ti. Le enseñará cómo funciona substring() , cuáles son las ventajas de dicho enfoque y cuándo puede ocasionar grandes problemas .