Estaba ocupado con la implementación lógica del proyecto durante los días de semana. Tenía algo de tiempo el sábado, así que saqué la versión gruesa en inglés de Thinking In Java de la estantería y leí sobre el empalme de objetos de cadena. Haga una traducción con referencia a este libro, agregue sus propios pensamientos y escriba este artículo para registrarlo.
Objeto de cadena inmutable
En Java, los objetos String son inmutables. En el código, puedes crear varios alias para un objeto String. Pero todos estos alias se refieren a lo mismo.
Por ejemplo, s1 y s2 son ambos alias del objeto "droidyue.com", y los alias almacenan referencias a los objetos reales. Entonces s1 = s2
Copie el código de código de la siguiente manera:
Cadena s1 = "droidyue.com";
Cadena s2 = s1;
System.out.println("s1 y s2 tienen la misma referencia =" + (s1 == s2));
El único operador sobrecargado en Java
En Java, el único operador sobrecargado está relacionado con la concatenación de cadenas. +,+=. Además, los diseñadores de Java no permiten la sobrecarga de otros operadores.
Análisis de empalme
¿Existe realmente un costo de rendimiento?
Después de comprender los dos puntos anteriores, es posible que tenga esta idea. Dado que los objetos Sting son inmutables, unir varias cadenas (tres o más) inevitablemente producirá objetos String intermedios redundantes.
Copie el código de código de la siguiente manera:
Cadena nombre de usuario = "Andy";
Edad de la cadena = "24";
Trabajo de cadena = "Desarrollador";
Información de cadena = nombre de usuario + edad + trabajo;
Para obtener la información anterior, el nombre de usuario y la edad se unirán para generar un objeto de cadena temporal t1, el contenido es Andy24, y luego se unirán t1 y el trabajo para generar el objeto de información final que necesitamos. Se genera el intermedio t1 y se crea t1. Posteriormente, si no hay reciclaje activo, inevitablemente ocupará una cierta cantidad de espacio. Si se trata de un empalme de muchas cadenas (asumiendo cientos de ellas, principalmente en llamadas a toString de objetos), el costo será aún mayor y el rendimiento se reducirá mucho.
Procesamiento de optimización del compilador
¿Existe realmente un costo de rendimiento anterior? ¿No existe una optimización de procesamiento especial para la concatenación de cadenas que se usa tan comúnmente? La respuesta es sí. Esta optimización se realiza cuando el compilador compila .java en código de bytes.
Si un programa Java quiere ejecutarse, debe pasar por dos períodos: tiempo de compilación y tiempo de ejecución. Durante la compilación, el compilador de Java (Compilador) convierte el archivo java en código de bytes. En tiempo de ejecución, la Máquina Virtual Java (JVM) ejecuta el código de bytes generado en el momento de la compilación. A lo largo de estos dos períodos, Java logró la llamada compilación en un solo lugar y se ejecuta en todas partes.
Experimentemos con las optimizaciones que se han realizado durante la compilación y podremos crear un fragmento de código que puede tener una penalización en el rendimiento.
Copie el código de código de la siguiente manera:
Concatenación de clase pública {
público estático vacío principal (String [] argumentos) {
Cadena nombre de usuario = "Andy";
Edad de la cadena = "24";
Trabajo de cadena = "Desarrollador";
Información de cadena = nombre de usuario + edad + trabajo;
System.out.println(información);
}
}
Compilar Concatenación.java. getConcatenation.clase
Copie el código de código de la siguiente manera:
javacConcatenación.java
Luego usamos javap para descompilar el archivo Concatenation.class compilado. javap -c Concatenación. Si no se encuentra el comando javap, considere agregar el directorio donde se encuentra javap a la variable de entorno o usar la ruta completa a javap.
Copie el código de código de la siguiente manera:
17:22:04-androidyue~/workspace_adt/strings/src$ javap -c Concatenación
Compilado de "Concatenación.java"
Concatenación de clase pública {
Concatenación pública();
Código:
0: carga_0
1: invocar especial #1 // Método java/lang/Object."<init>":()V
4: regreso
principal vacío estático público (java.lang.String []);
Código:
0: ldc #2 // Cadena Andy
2: astore_1
3: ldc #3 // Cadena 24
5: astore_2
6: ldc #4 // Desarrollador de cadenas
8: astore_3
9: nuevo #5 // clase java/lang/StringBuilder
12: doble
13: invocar especial #6 // Método java/lang/StringBuilder."<init>":()V
16: carga_1
17: invocarvirtual #7 // Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: carga_2
21: invocarvirtual #7 // Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: carga_3
25: invocarvirtual #7 // Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invocarvirtual #8 // Método java/lang/StringBuilder.toString:()Ljava/lang/String;
31: una tienda 4
33: getstatic #9 // Campo java/lang/System.out:Ljava/io/PrintStream;
36: carga 4
38: invocarvirtual #10 // Método java/io/PrintStream.println:(Ljava/lang/String;)V
41: regreso
}
Entre ellos, ldc, astore, etc. son instrucciones de código de bytes de Java, similares a las instrucciones de ensamblaje. Los siguientes comentarios utilizan contenido relacionado con Java como explicación. Podemos ver que hay muchos StringBuilders arriba, pero no los llamamos explícitamente en el código Java. Esta es la optimización realizada por el compilador de Java. Cuando el compilador de Java encuentra empalme de cadenas, creará un objeto StringBuilder, y lo siguiente. El empalme en realidad llama al método append del objeto StringBuilder. De esta forma no habrá problemas que nos preocupaban anteriormente.
¿Optimización del compilador sola?
Dado que el compilador ha realizado la optimización por nosotros, ¿es suficiente confiar simplemente en la optimización del compilador?
A continuación vemos un fragmento de código no optimizado con menor rendimiento.
Copie el código de código de la siguiente manera:
public void implicitUseStringBuilder (valores de cadena []) {
Resultado de cadena = "";
for (int i = 0; i < valores.longitud; i ++) {
resultado += valores[i];
}
System.out.println(resultado);
}
Utilice javac para compilar y javap para ver
Copie el código de código de la siguiente manera:
vacío público implícitoUseStringBuilder(java.lang.String[]);
Código:
0: ldc #11 // Cadena
2: astore_2
3: iconost_0
4: istore_3
5: cargar_3
6: carga_1
7: longitud de la matriz
8: if_icmpge 38
11: nuevo #5 // clase java/lang/StringBuilder
14: doble
15: invocar especial #6 // Método java/lang/StringBuilder."<init>":()V
18: carga_2
19: invocarvirtual #7 // Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: carga_1
23: cargar_3
24: una carga
25: invocarvirtual #7 // Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invocarvirtual #8 // Método java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: ir a 5
38: getstatic #9 // Campo java/lang/System.out:Ljava/io/PrintStream;
41: carga_2
42: invocarvirtual #10 // Método java/io/PrintStream.println:(Ljava/lang/String;)V
45: regreso
Entre ellos, 8: if_icmpge 38 y 35: goto 5 forman un bucle. 8: if_icmpge 38 significa que si la comparación de enteros de la pila de operandos JVM es mayor o igual que (el resultado opuesto de i <values.length), salte a la línea 38 (System.out). 35: ir a 5 significa saltar directamente a la línea 5.
Pero una cosa muy importante aquí es que la creación de objetos StringBuilder ocurre entre bucles, lo que significa cuántos objetos StringBuilder se crearán en tantos bucles, lo que obviamente no es bueno. Código desnudo de bajo nivel.
Un poco de optimización puede mejorar instantáneamente su rendimiento.
Copie el código de código de la siguiente manera:
public void explicitUseStringBuider(String[] valores) {
Resultado de StringBuilder = nuevo StringBuilder();
for (int i = 0; i < valores.longitud; i ++) {
resultado.append(valores[i]);
}
}
Información recopilada correspondiente
Copie el código de código de la siguiente manera:
public void explicitUseStringBuider(java.lang.String[]);
Código:
0: nuevo #5 // clase java/lang/StringBuilder
3: doble
4: invocar especial #6 // Método java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconost_0
9: istore_3
10: cargar_3
11: carga_1
12: longitud de la matriz
13: if_icmpge 30
16: carga_2
17: carga_1
18: cargar_3
19: una carga
20: invocarvirtual #7 // Método java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: pop
24: iinc 3, 1
27: ir a 10
30: regreso
Como se puede ver en lo anterior, 13: if_icmpge 30 y 27: ir a 10 forman un bucle y 0: nuevo #5 está fuera del bucle, por lo que StringBuilder no se creará varias veces.
En general, debemos intentar evitar la creación implícita o explícita de StringBuilder en el cuerpo del bucle. Por lo tanto, aquellos que entienden cómo se compila el código y cómo se ejecuta internamente pueden escribir código de nivel superior.
Si hay algún error en el artículo anterior, critíquelo y corríjalo.