Asignación de memoria de cadenas y operaciones de empalme

Características básicas de la cadena

  • Cadena: Cadena, representada por un par ""de comillas
  • Dos formas de instanciar String: String s1 = "hello";,String s2 = new String("hello");
  • La cadena se declara final y no se puede heredar
  • String implementa la interfaz serializable: lo que indica que la cadena admite la serialización.
  • String implementa la interfaz Comparable: lo que indica que la cadena se puede comparar en tamaño

Cambios en la estructura de almacenamiento de cadenas en jdk9

  • String define un valor char[] final (usando dos bytes (16 bits) por carácter) internamente en jdk8 y versiones anteriores para almacenar datos de cadenas, pero los datos recopilados de muchas aplicaciones diferentes muestran que las cadenas son El componente principal utilizado por el montón, la mayoría de las cadenas los objetos contienen solo caracteres Latin-1 y requieren solo un byte de almacenamiento.
  • Después de jdk9, el método de representación interna de la clase String cambia de una matriz de caracteres UTF-16 a una matriz de bytes (byte[]) más el campo de marca de codificación. La nueva clase String almacenará codificaciones de caracteres en ISO-8859-1/Latin-1 (un byte por carácter) o UTF-16 (dos bytes por carácter) según el contenido de la cadena. El indicador de codificación indicará qué codificación se utiliza.
  • Conclusión: la cadena ya no se almacena en char[], sino que se cambia a byte[] más la marca de codificación, lo que reduce el uso de la memoria durante el trabajo y reduce en gran medida las actividades del GC.

Cadena: secuencia inmutable de caracteres

Cuando a una cadena se le asigna un valor literal ( String str="Hello";), el valor de la cadena se declara en el conjunto de constantes de cadena. En el grupo de constantes de cadenas, las cadenas con el mismo contenido no se almacenarán.

Conjunto de constantes de cadena: el conjunto
de cadenas de String es una tabla hash de tamaño fijo. Si hay demasiados Strings en el String Pool, se producirán graves conflictos Hash, lo que dará como resultado una lista enlazada larga, y el impacto directo de una lista enlazada larga es que el rendimiento se reducirá significativamente cuando se llame a String.intern. Use -XX:StringTablesize para establecer la longitud de StringTable

  • En jdk6, StringTable es fijo, es decir, la longitud de 1009, por lo que si hay demasiadas cadenas en el grupo constante, la eficiencia disminuirá rápidamente y no hay ningún requisito para la configuración de StringTablesize.
  • En jdk7, la longitud predeterminada de StringTable es 60013, y no hay requisitos para la configuración de StringTablesize
  • En jdk8, si establece la longitud de StringTable, 1009 es el valor mínimo que se puede establecer

Asignación de memoria de cadena

Hay 8 tipos de datos básicos y un tipo especial String en el lenguaje Java. Estos tipos proporcionan el concepto de un grupo constante para hacerlos más rápidos y más eficientes con la memoria durante la operación.

El grupo de constantes es como un caché proporcionado a nivel del sistema Java. El sistema coordina las agrupaciones constantes de los 8 tipos de datos básicos. La agrupación constante de tipo String es especial. Hay dos métodos principales de almacenamiento:

  • Los objetos de cadena declarados directamente con comillas dobles se almacenan directamente en el grupo de constantes.
  • Si el objeto String no se declara con comillas dobles, puede utilizar el método interno() proporcionado por String.

Después de JDK 7, las cadenas internas ya no se asignan en la generación permanente del montón de Java (la frecuencia de recolección de basura de generación permanente es baja) , sino que se asignan en la parte principal del montón de Java (jóvenes y antiguos) , todas las cadenas se almacenan en el montón, junto con otros objetos creados por la aplicación. Este cambio dará como resultado que residan más datos en el almacenamiento dinámico principal de Java y menos datos en la generación permanente, por lo que es posible que sea necesario cambiar el tamaño del almacenamiento dinámico.

inserte la descripción de la imagen aquí

El efecto obvio de este cambio se puede ver en aplicaciones grandes que cargan muchas clases o hacen un uso intensivo del método String.intern().

Ejemplo:

class Memory {
    
    
    public static void main(String[] args) {
    
    //line 1
        int i= 1;//line 2
        Object obj = new Object();//line 3
        Memory mem = new Memory();//Line 4
        mem.foo(obj);//Line 5
    }//Line 9
    private void foo(Object param) {
    
    //line 6
        String str = param.toString();//line 7
        System.out.println(str);
    }//Line 8
}

inserte la descripción de la imagen aquí

Operación de concatenación de cadenas

  • El resultado del empalme de constante y constante está en el grupo constante, el principio es la optimización en tiempo de compilación
  • Una variable con el mismo contenido no existirá en el grupo constante
  • Siempre que uno de ellos sea una variable, el resultado está en el montón. El principio del empalme de variables es StringBuilder
  • Si el resultado del empalme llama al método interno (), colocará activamente el objeto de cadena que aún no está en el grupo constante en el grupo y devolverá la dirección de este objeto

Ejemplo 1:

public static void test1() {
    
    
    // 都是常量,前端编译期会进行代码优化
    String s1 = "a" + "b" + "c";  
    String s2 = "abc"; 

    // true,有上述可知,s1和s2实际上指向字符串常量池中的同一个值
    System.out.println(s1 == s2); 
}

Después de descompilar en un archivo de clase, encontrará String s1 = "abc", lo que indica que el código está optimizado en tiempo de compilación
inserte la descripción de la imagen aquí

Ejemplo 2:

public static void test2() {
    
    
    String s1 = "javaEE";
    String s2 = "hadoop";

    String s3 = "javaEEhadoop";
    String s4 = "javaEE" + "hadoop";    
    String s5 = s1 + "hadoop";
    String s6 = "javaEE" + s2;
    String s7 = s1 + s2;

    System.out.println(s3 == s4); // true 编译期优化
    System.out.println(s3 == s5); // false s1是变量,不能编译期优化
    System.out.println(s3 == s6); // false s2是变量,不能编译期优化
    System.out.println(s3 == s7); // false s1、s2都是变量
    System.out.println(s5 == s6); // false s5、s6 不同的对象实例
    System.out.println(s5 == s7); // false s5、s7 不同的对象实例
    System.out.println(s6 == s7); // false s6、s7 不同的对象实例

    String s8 = s6.intern();
    System.out.println(s3 == s8); // true intern之后,s8和s3一样,指向字符串常量池中的"javaEEhadoop"
}

Principio de empalme de variables:
Cuando se agregan dos variables: String s1=“a”;String s2=“b”;: Los detalles de ejecución son los siguientes:
①StringBuilder s=new StringBuilder();
②s.append(“a”);
③s .append("b");
④s.toString();

Ejemplo 3:

public void test3(){
    
    
    String s0 = "ab";
    String s1 = "a";
    String s2 = "b";
    String s3 = s1 + s2;
    System.out.println(s0 == s3); // false s3指向对象实例,s0指向字符串常量池中的"ab"
    String s7 = "cd";
    final String s4 = "c";
    final String s5 = "d";
    String s6 = s4 + s5;
    System.out.println(s6 == s7); // true s4和s5是final修饰的,编译期就能确定s6的值了
}

String Builder no se usa necesariamente para operaciones de empalme de cadenas.Si se usa la modificación final, es constante y la optimización del código se realizará en el compilador. Si no se usa la modificación final, es una variable, que se empalmará a través del nuevo StringBuilder. En el desarrollo real, puede usar final, intente usarlo.

Ejemplo 4:

Comparación del rendimiento de la operación de concatenación de cadenas:

public class Test{
    
        
	public static void main(String[] args) {
    
            
		int times = 40000;        
		
		long start = System.currentTimeMillis();        
		
		testString(times);    // String  6963ms    
		//testStringBuilder(times); // StringBuilder    2ms             
		
		long end = System.currentTimeMillis();        
		System.out.println("String: " + (end-start) + "ms");        
		
	
	}    
	
	public static void testString(int times) {
    
            
		String str = "";        
		for (int i = 0; i < times; i++) {
    
                
			str += "test";        
		}    
	}    
	
	public static void testStringBuilder(int times) {
    
            
		StringBuilder sb = new StringBuilder();        
		for (int i = 0; i < times; i++) {
    
                
			sb.append("test");        
		}    
	}    

}

Resultado: la eficiencia de agregar cadenas a través del método append() de StringBuilder es mucho mayor que la de usar el método de empalme de cadenas de Sting.

Detalles: método append() de StringBuilder: solo se ha creado un objeto StringBuilder de principio a fin. Con el método de empalme de cadenas de String, se crearán varios objetos StringBuilder y String durante el proceso de ejecución, lo que ocupa una gran cantidad de memoria. Si GC es realizado, costará más tiempo extra.

uso de interno()

interno (): intente colocar el objeto de cadena en el grupo de cadenas. Primero, determine si hay un valor de cadena correspondiente en el grupo de constantes de cadenas. Si existe, devuelva la dirección de la cadena en el grupo de constantes. Si no es así existe, estará en el grupo constante Agregue la cadena y devuelva la dirección correspondiente.

intern es un método nativo que llama al método C subyacente.

La cadena interna garantiza que solo haya una copia de la cadena en la memoria, lo que puede ahorrar espacio en la memoria y acelerar la ejecución de tareas de manipulación de cadenas. Tenga en cuenta que este valor se almacenará en String Intern Pool.

Eficiencia de espacio: cuando se usa una gran cantidad de cadenas existentes en el programa, especialmente cuando hay muchas cadenas repetidas, usar el método intern() puede ahorrar espacio en la memoria.


Pregunta de la entrevista: ¿Cuántos objetos creará el nuevo String("ab")?

String s = new String("ab");Se crean dos objetos: un nuevo objeto en el espacio de almacenamiento dinámico y una constante de cadena "ab" en el grupo de constantes de cadena (si la constante ya existe en el grupo de constantes de cadena en este momento, no se creará)
inserte la descripción de la imagen aquí
Preguntas de la entrevista: nuevo String("¿Cuántos objetos creará a")+nuevo String("b")?
inserte la descripción de la imagen aquí


Uso de interno:
inserte la descripción de la imagen aquí

El uso de interno en jdk6: falso falso
inserte la descripción de la imagen aquí
String s = new String("1")Creó dos objetos (objeto nuevo, constante de cadena)
② s.intern() Dado que "1" ya existe en el conjunto de constantes de cadena, s apunta a la dirección del objeto en el espacio de almacenamiento dinámico, s2 apunta a la dirección de "1" en el grupo de constantes en el espacio de almacenamiento dinámico
③: La dirección del registro de la variable s3 es: new String("11");, pero no se genera ninguna cadena en el grupo de constantes de cadena "11"
④: s3.intern() está en la constante de cadena pool (un "11"nuevo objeto)
⑤: s4 apunta a la dirección en el conjunto de cadenas constantes "11", por lo que las direcciones a las que apuntan s3 y s4 son diferentes

El uso de interno en jdk7/8: falso verdadero

inserte la descripción de la imagen aquí
En jdk7/8, s3.intern(), dado que ya existe new String("11");, generará una dirección de referencia de "11" en el conjunto de constantes new String("11");, y s4 apuntará a la referencia de "11" generada en el conjunto de constantes cuando la línea anterior del código se ejecuta la dirección, por lo que tanto s3 como s4 apuntan a la misma dirección.

Resumen:
①: En jdk6, si hay uno en el grupo de cadenas, no se colocará. Devuelve la dirección del objeto en el grupo de cadenas existente ; si no, hará una copia del objeto, lo colocará en el grupo de cadenas y devolverá la dirección del objeto en el grupo de cadenas.
②: En jdk7/8, si hay una cadena en el grupo, no se colocará. Devuelve la dirección de un objeto en el conjunto de constantes de cadena existente . De lo contrario, copiará la dirección de referencia del objeto, la colocará en el grupo de cadenas y devolverá la dirección de referencia en el grupo de cadenas.


Ejercicio:
inserte la descripción de la imagen aquí
En jdk6: s.intern() crea una cadena "ab" en el conjunto de constantes de cadena, s2 apunta a "ab", resultado de la ejecución: verdadero falso
inserte la descripción de la imagen aquí
En jdk7/8: s.intern() no crea una cadena" ab", pero se crea una referencia que apunta a una nueva cadena ("ab"); tanto s como s2 apuntan a esta dirección, resultado de la ejecución: verdadero verdadero
inserte la descripción de la imagen aquí


Supongo que te gusta

Origin blog.csdn.net/Lzy410992/article/details/118707321
Recomendado
Clasificación