Después de leer este artículo, entiendo StringTable

estudia mucho, mejora cada día

Este artículo se ha incluido en mi almacén de Github DayDayUP : github.com/RobodLee/DayDayUP, bienvenido a Star, para obtener más artículos, vaya a: Navegación de directorios

Prefacio

String debería ser la clase más utilizada en Java. Pocos programas Java no utilizan String. Crear objetos en Java requiere mucho rendimiento y, a menudo, usamos los mismos objetos String, por lo que crear estos mismos objetos no es una pérdida de rendimiento. Por lo tanto, existe una existencia especial de StringTable, StringTable se denomina grupo de constantes de cadena, se usa para almacenar constantes de cadena, de modo que cuando usamos el mismo objeto de cadena, podemos obtenerlo directamente de StringTable sin volver a crear el objeto. Entonces, ¿cuáles son las características de StringTable? A continuación, echemos un vistazo más de cerca a StringTable.

Algunas características de String

Inmutabilidad de la cuerda

Antes de hablar sobre StringTable, debo mencionar la inmutabilidad de String, porque la realización de StringTable solo es posible cuando String es inmutable. Cuando definimos una cadena:

String s = "hello";

En este momento, "hola" se almacena en StringTable, y la variable s es una referencia y s apunta a "hola" en StringTable.

Cuando cambiamos el valor de s, lo cambiamos a "hola mundo"

String s = "hello";
s = "hello world";

En este momento, en lugar de cambiar el valor de "hola" señalado por sa "hola mundo", apunta a una nueva cadena.

Cómo verificar que apunta a una nueva cadena en lugar de modificar su contenido, podemos imprimir el valor hash para verlo.

        String s = "hello";
        System.out.println(System.identityHashCode(s));
        s = "hello world";
        System.out.println(s.hashCode());
        s = "hello";
        System.out.println(System.identityHashCode(s));

Se puede ver que el valor hash de la primera vez y la tercera vez son iguales, y el valor hash de la segunda vez es diferente de las otras dos veces, lo que indica que de hecho está apuntando a un nuevo objeto en lugar de modificar el valor de String.

Entonces, ¿cómo ** String logra la inmutabilidad? ** Echemos un vistazo al código fuente de la clase String:

En el código fuente, podemos ver que en primer lugar la clase String es final, lo que indica que no se puede heredar, y sus características inmutables no serán cambiadas por subclases; en segundo lugar, la capa inferior de String es en realidad una matriz modificada por final, indicando este valor. Después de determinar el valor, no puede apuntar a una nueva matriz. Aquí tenemos que dejar en claro que aunque la matriz modificada final no puede apuntar a una nueva matriz, el valor de la matriz se puede modificar:

Dado que se puede modificar, ¿por qué String es inmutable? Debido a que la clase String no proporciona ningún método para modificar el valor de la matriz, la inmutabilidad de String se debe a su implementación subyacente, no a una final.

Entonces, ¿por qué debería diseñarse String para ser inmutable? ** Creo que se debe a consideraciones de seguridad. Solo imagina que en un programa, hay varios lugares que hacen referencia al mismo objeto String al mismo tiempo, pero es posible que desees modificar el contenido del String en un solo lugar. Si el String es Variable, que hace que se modifiquen los contenidos de todas las cadenas. En caso de que esto sea en un escenario importante, como transmitir una contraseña, ¿no causaría un gran problema? Entonces String está diseñado para ser inmutable.

Concatenación de cadenas

Después de hablar sobre la inmutabilidad de String, hablemos sobre el problema del empalme de cuerdas. Mira el siguiente programa

public static void main(String[] args) {
    String a = "hello";
    String b = " world!";
    String c = a+b;
}

Es así de simple ¿Sabes cómo se implementa? Echemos un vistazo a las instrucciones de código de bytes correspondientes a este código:

No explicaré el significado de estas instrucciones de código de bytes línea por línea. Centrémonos en las pocas líneas de código marcadas en rojo. No importa si no comprende las instrucciones de código de bytes anteriores, puede leer los comentarios a continuación. Como puede ver, el empalme de cadenas en realidad está llamando al método append () de StringBuilder y luego llamando al método toString () para devolver una nueva cadena.

StringTable explicado

Cuándo se pone la cadena en StringTable

Primero presentemos brevemente StringTable. Su estructura de datos subyacente es HashTable , y cada elemento es una estructura clave-valor , que utiliza una implementación de matriz + lista enlazada individualmente.

Mira el siguiente fragmento de código:

public static void main(String[] args) {
    
    
 -> String a = "hello";
    String b = " world!";
    String c = "hello world!";
}

Una vez cargada la clase, las cadenas "hola" se cargan en el grupo de constantes de tiempo de ejecución como símbolos y aún no se han convertido en objetos de cadena. Esto se debe a que las cadenas en Java utilizan un mecanismo de carga diferida , que es el programa Carga cuando se ejecuta a una línea específica. Por ejemplo, cuando el programa se ejecuta en la línea señalada por la flecha, "hola" cambiará de un símbolo a un objeto de cadena y luego irá a StringTable para averiguar si hay el mismo objeto de cadena y, de ser así, devolverá la dirección correspondiente a Variable a, si no, ponga "hola" en StringTable, y luego dé la dirección a la variable a. Veamos si este es el caso:

        String s1 = "hello world";
        String s2 = "hello world";
        String s3 = "hello world";
        String s4 = "hello world";
        System.out.println(System.identityHashCode(s1));
        System.out.println(System.identityHashCode(s2));
        System.out.println(System.identityHashCode(s3));
        System.out.println(System.identityHashCode(s4));

Se puede ver que los valores hash de los cuatro objetos de cadena son los mismos, lo que indica que si el mismo objeto ya existe en StringTable, apuntará al mismo objeto en lugar de apuntar a un nuevo objeto.

¿Qué hiciste cuando new String ()

Cuando usamos new String () para crear un objeto de cadena, no es lo mismo que escribir String a = "hola" directamente. El primero se almacena en la memoria del montón y el segundo se almacena en StringTable.

De hecho, StringTable también está en el montón, lo explicaré en detalle más adelante. Primero verifiquemos la declaración anterior:

        String a = "hello";
        String b = new String("hello");
        System.out.println(a == b);

Mira los resultados de ejecución:

El resultado es obviamente falso, lo que indica que los dos de hecho no son el mismo objeto. Además, como se mencionó anteriormente, cuando apunte a una constante de cadena, primero buscará en StringTable y devolverá directamente la cadena encontrada cuando se encuentre, pero este no es el caso con new String (). Cada nueva cadena creará una nueva cadena en el montón. Objetos, incluso si tienen el mismo contenido, por ejemplo, creo 4 objetos String.

String s1 = new String("hello world");
String s2 = new String("hello world");
String s3 = new String("hello world");
String s4 = new String("hello world");

En este momento, habrá 4 objetos String en el montón:

Imprimamos el hash nuevamente para ver si hay 4 objetos:

System.out.println(System.identityHashCode(s1));
System.out.println(System.identityHashCode(s2));
System.out.println(System.identityHashCode(s3));
System.out.println(System.identityHashCode(s4));

Se puede ver en los resultados que de hecho hay 4 objetos diferentes.

¿Cuál es el método interno?

Primero veamos un fragmento de código:

String s1 = new String("hello world");
String s2 = "hello world";
String s3 = s1.intern();

System.out.println(s1 == s2);
System.out.println(s2 == s3);

Veamos si puedes analizar el resultado. Si ya conoces el resultado, significa que has dominado el método interno. Si no lo sabes, mira mi explicación a continuación.

El resultado es falso y verdadero ¿Qué hace el método interno?

La función del método interno es intentar poner una cadena en StringTable, si no existe, ponerla en StringTable y devolver la dirección en StringTable, si existe, devolverá directamente la dirección en StringTable. Esta es la función del método interno en la versión jdk1.8. Hay algunas diferencias en la versión jdk1.6. En la 1.6, el interno intenta poner el objeto string en la StringTable. Si lo hay, no se colocará. Si no, copiará este objeto. , Colóquelo en StringTable y devuelva el objeto en StringTable. Pero no discutiremos la versión 1.6 aquí.

Explica el código anterior: primero creamos un objeto de cadena "hola mundo" en el montón, y s1 apunta al objeto en este montón; luego creamos un objeto constante de cadena con el valor "hola mundo" en StringTable, s2 apunta al objeto en esta StringTable; finalmente intentamos poner el objeto en el montón al que apunta s1 en StringTable, y encontramos que ya existe, por lo que devolvemos la dirección del objeto string en StringTable a s3. Entonces s1 y s2 apuntan al mismo objeto, y s2 y s3 ​​son el mismo objeto. Como en la imagen de abajo:

¿Qué pasa si cambias un poco el código?

String s1 = new String("hello world").intern();
String s2 = "hello world";

System.out.println(s1 == s2);

En este momento, el resultado es cierto . Analicémoslo: primero usamos new String () para crear un objeto de cadena en el montón, y luego llamamos a su método intern (), así que buscamos en StringTable la misma cadena y encontramos que no había ninguna cadena. Ponlo en StringTable, y luego da la dirección del objeto en StringTable a s1; cuando se trata de la segunda fila, debido a que no se usa new String (), se busca directamente desde StringTable, y si se encuentra, el objeto en StringTable es La dirección se asigna a s2, por lo que s1 y s2 apuntan al mismo objeto.

Ubicación de StringTable

Como se mencionó anteriormente, StringTable está en el montón, ahora verifiquémoslo. El método de verificación es muy simple: colocamos una gran cantidad de cadenas en el desbordamiento de la memoria y podemos ver dónde está StringTable al ver qué parte de la memoria se desborda.

ArrayList list = new ArrayList();
String str = "hello";
for(int i = 0;i < Integer.MAX_VALUE;i++) {
    
    
    String s = str + i;
    str = s;
    list.add(s.intern());
}

Primero llamamos al método interno para poner la cadena en StringTable, y luego usamos una ArrayList para almacenar la cadena, el propósito es evitar la recolección de basura, porque de esta manera, cada cadena estará fuertemente entre comillas y no será recolectada como basura. Después del reciclaje, no veremos los resultados que queremos. Mira los resultados:

Obviamente, se produjo un desbordamiento de memoria en la memoria del montón, por lo que se puede determinar StringTable是存放在堆中的. Pero esto es de la versión 1.7 y se guardó en la generación permanente antes de la 1.7.

Recolección de basura de StringTable

Dado que la recolección de basura se mencionó anteriormente, verifiquemos si la recolección de basura ocurrirá en StringTable. Sigue siendo el código anterior, pero ligeramente modificado:

String str = "hello";
for(int i = 0;i < 10000;i++) {
    
    
    String s = str + i;
    s.intern();
}

No es necesario poner la cadena en ArrayList; de lo contrario, incluso si se produce un desbordamiento de memoria, no se recogerá la basura. Para ver el proceso de recolección de basura, agregamos algunos parámetros de la máquina virtual sin especificar el tamaño del montón:

Ejecute el programa para ver la situación de la impresión:

Debido a que la memoria del montón es lo suficientemente grande, no se realiza la recolección de basura. Ahora configuraremos la memoria del montón en un tamaño más pequeño y llegaremos a 1 m:

-Xmx1m

Ejecutemos el programa nuevamente:

Esta vez, debido a la memoria del montón insuficiente, se produjeron varias recolecciones de basura. Por lo tanto, StringTable también provocará la recolección de basura debido a la memoria insuficiente .

Implementación de StringTable y ajuste de rendimiento de bajo nivel

Antes de introducir el ajuste de rendimiento, tengo que hablar sobre la implementación subyacente de StringTable. Como se mencionó anteriormente, la StringTable subyacente es una HashTable. ¿Cómo se ve HashTable? De hecho, es una matriz + lista vinculada, cada elemento es un valor-clave. Cuando se almacena un elemento, su clave se calcula mediante la función hash para obtener el subíndice del arreglo y se almacena en la posición correspondiente.

Por ejemplo, hay un valor-clave y el resultado de la función hash es 2 calculado por la clave, luego el valor se almacena en la posición del subíndice 2 del arreglo. Pero si hay otra tecla que calcula el mismo resultado mediante la función hash, por ejemplo, también es 2, pero la posición de 2 ya tiene un valor, este fenómeno se llama conflicto hash , ¿cómo solucionarlo? El método de lista enlazada se utiliza aquí:

El método de la lista enlazada consiste en encadenar elementos con el mismo subíndice en forma de lista enlazada. Si la capacidad de la matriz es pequeña pero hay muchos elementos, la probabilidad de colisiones hash aumentará. Todo el mundo sabe que la eficiencia de las listas enlazadas es mucho menor que la de las matrices, y demasiadas colisiones hash afectarán al rendimiento. Por lo tanto, para reducir la probabilidad de colisiones hash, el tamaño de la matriz se puede aumentar de manera apropiada. Cada celda de la matriz se denomina depósito en StringTable. Podemos aumentar la cantidad de depósitos para mejorar el rendimiento. El número predeterminado es 60013. Veamos una comparación:

long startTime = System.nanoTime();
String str = "hello";
for(int i = 0;i < 500000;i++) {
    
    
    String s = str + i;
    s.intern();
}
long endTime = System.nanoTime();
System.out.println("花费的时间为:"+(endTime-startTime)/1000000 + "毫秒");

Primero especifique un depósito más pequeño mediante un parámetro de máquina virtual, hagamos 2000:

 -XX:StringTableSize=2000

Ejecutarlo:

Tomó 1.2 segundos en total. Aumentemos un poco la cantidad de cubos y lleguemos a 20000:

 -XX:StringTableSize=20000

Ejecutarlo:

Como puede ver, esta vez solo tomó 0.19 segundos, y el rendimiento se mejoró significativamente, lo que indica que esto puede optimizar StringTable. Aquí solo se presenta un método para mejorar el rendimiento. El espacio es limitado, así que no diré más. Puedo escribir un artículo específicamente para hablar sobre la optimización del rendimiento de StringTable en el futuro.

Escribir al final

Este es el final del artículo. Puede haber algunas omisiones en el contenido, por lo que todos lo verán. Después de todo, el nivel es limitado. Si crees que mi artículo es útil para ti, no olvides marcar como favorito, reenviar, marcar y seguir.

Cuenta pública de WeChat

Supongo que te gusta

Origin blog.csdn.net/weixin_43461520/article/details/106932922
Recomendado
Clasificación