En el siguiente código, cuando llega al comentario, si el GC no se ejecuta, se crean aproximadamente 1000 objetos (según el libro de OCA), el StringBuilder se modifica y permanece como un solo objeto, la cadena vacía {{X1 }} se agrupa y se reutiliza, eso es todo lo que se explica. no es el argumento s a new String("s") que necesita GC, y i, no se convertirá primero en un objeto new String, luego se combinará con {{X6 }} crea otro objeto new String convirtiéndolos en 2 objetos String en esa línea, elegibles para GC junto con el argumento de append, un total de 3 objetos String en cada bucle. Entonces, ¿una suma de 3000 objetos cuando el código llega a la línea de comentarios?

public class Mounds {

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        String s = new String();
        for (int i = 0; i < 1000; i++) {
            s = " " + i;
            sb.append(s);
        }
// done with loop
    }
}
1
user5488307 7 mar. 2018 a las 00:35

3 respuestas

La mejor respuesta

Si compilamos este código y observamos el bytecode generado, podemos examinarlo exactamente

  public static void main(java.lang.String[]) throws java.io.IOException;
Code:
   0: new           #19                 // class java/lang/StringBuilder
   3: dup
   4: invokespecial #21                 // Method java/lang/StringBuilder."<init>":()V
   7: astore_1
   8: new           #22                 // class java/lang/String
  11: dup
  12: invokespecial #24                 // Method java/lang/String."<init>":()V
  15: astore_2
  16: iconst_0
  17: istore_3
  18: goto          47
  21: new           #19                 // class java/lang/StringBuilder
  24: dup
  25: ldc           #25                 // String
  27: invokespecial #27                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  30: iload_3
  31: invokevirtual #30                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  34: invokevirtual #34                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  37: astore_2
  38: aload_1
  39: aload_2
  40: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  43: pop
  44: iinc          3, 1
  47: iload_3
  48: sipush        1000
  51: if_icmplt     21
  54: return

Las instrucciones que nos interesan son del 21 al 40. En 21, hay un segundo StringBuilder creado, volveremos a eso más adelante.

En 25 Vemos, que hay un ldc, lo que significa que se empuja un literal a la pila, en este caso es la Cadena literal "".

Entonces sucede la verdadera magia. Se llama al constructor del segundo StringBuilder, que toma el literal de la pila como argumento. Luego, int i se carga desde la matriz de variables locales con iload_3, luego se llama al método append del segundo StringBuilder para agregar i a él, y luego se llama a toString. Con astore_2 y aload_1 se almacena el valor de retorno de la llamada toString, y se carga el primer StringBuilder, y luego se vuelve a cargar el String. Y, finalmente, se llama al método append del primer StringBuilder para agregar ese nuevo String al StringBuilder.

Resulta que hay un nuevo StringBuilder creado en cada ciclo, porque cada vez que usas "" + i, se debe crear un StringBuilder para concatenar el String y el int. Además, se creará una nueva Cadena mediante el método toString del StringBuilder intermedio, por lo que habrá un total de 2000 Objetos allí.

Una versión mejor se vería así:

for (int i = 0; i < 1000; i++) {
        sb.append(' ');
        sb.append(i);
    }

Eso creará el siguiente código de bytes:

  public static void main(java.lang.String[]) throws java.io.IOException;
Code:
   0: new           #19                 // class java/lang/StringBuilder
   3: dup
   4: invokespecial #21                 // Method java/lang/StringBuilder."<init>":()V
   7: astore_1
   8: new           #22                 // class java/lang/String
  11: dup
  12: invokespecial #24                 // Method java/lang/String."<init>":()V
  15: astore_2
  16: iconst_0
  17: istore_3
  18: goto          37
  21: aload_1
  22: bipush        32
  24: invokevirtual #25                 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
  27: pop
  28: aload_1
  29: iload_3
  30: invokevirtual #29                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  33: pop
  34: iinc          3, 1
  37: iload_3
  38: sipush        1000
  41: if_icmplt     21
  44: return

Podemos ver que ahora solo hay un StringBuilder, que recibe su método de agregar dos veces, por lo que no se asigna memoria aquí y esto debería ser mejor.

4
Alexander Daum 6 mar. 2018 a las 22:08

El compilador probablemente se da cuenta de que el alcance de la variable s está dentro del ciclo, así que alinea la asignación en el append() para producir

sb.append(" " + i)

Así que ahora solo la conversión de int crea una nueva cadena en cada iteración.

-1
Sharon Ben Asher 6 mar. 2018 a las 21:45

El uso óptimo sería:

    StringBuilder sb = new StringBuilder(4000);
    for (int i = 0; i < 1000; ++i) {
        sb.append(' ').append(i);
    }
    ... do something with sb.toString()

Como:

  • String s = new String(); crea una cadena vacía innecesaria. Igual que String s = "";. (Optimización no considerada).
  • s = " " + i; concatena dos cadenas en una nueva cadena. Una tarea que se debe dejar al StringBuilder, ya que ese es el único propósito de la misma.
  • Agregar un carácter ' ' es más eficiente que una Cadena " ".
  • new StringBuilder(4000) con una capacidad inicial que uno podría usar aquí, evitando la reasignación intermitente al agregar. 1000 números de los cuales 900 son 3 dígitos, más un espacio, caben en 4000 caracteres.
1
Joop Eggen 6 mar. 2018 a las 22:19