Pregunta StringBuilder vs Concatenación de cadenas en toString () en Java


Dado el 2 toString() implementaciones a continuación, cuál se prefiere:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

o

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}

?

Y lo que es más importante, dado que solo tenemos 3 propiedades, es posible que no marque la diferencia, pero ¿en qué momento pasaría de + concat a StringBuilder?


766
2017-10-07 15:44


origen


Respuestas:


La versión 1 es preferible porque es más corta y el compilador de hecho lo convertirá en la versión 2 - Sin diferencia de rendimiento en absoluto.

Más importante dado que tenemos solo 3   propiedades que podría no hacer una   diferencia, pero ¿en qué momento   cambiar de concat a constructor?

En el punto en el que estás concatenando en un bucle, normalmente es cuando el compilador no puede sustituir StringBuilder por sí mismo.


804
2017-10-07 15:51



La clave es si está escribiendo una sola concatenación en un solo lugar o acumulándola a lo largo del tiempo.

Para el ejemplo que proporcionó, no tiene sentido utilizar explícitamente StringBuilder. (Mire el código compilado para su primer caso).

Pero si está creando una cadena, p. dentro de un bucle, use StringBuilder.

Para aclarar, suponiendo que hugeArray contiene miles de cadenas, codifique así:

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}

es muy desperdiciador de tiempo y memoria comparado con:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();

204
2017-10-07 15:47



Yo prefiero:

String.format( "{a: %s, b: %s, c: %s}", a, b, c );

... porque es corto y legible.

me gustaría no optimice esto para la velocidad a menos que lo use dentro de un bucle con un conteo de repeticiones muy alto y han medido la diferencia de rendimiento.

Estoy de acuerdo en que, si tiene que generar muchos parámetros, este formulario puede resultar confuso (como dice uno de los comentarios). En este caso cambiaría a una forma más legible (tal vez usando ToStringBuilder de apache-commons - tomado de la respuesta de matt b) e ignorar el rendimiento nuevamente.


64
2017-10-07 15:47



En la mayoría de los casos, no verá una diferencia real entre los dos enfoques, pero es fácil construir el peor de los casos como este:

public class Main
{
    public static void main(String[] args)
    {
        long now = System.currentTimeMillis();
        slow();
        System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");

        now = System.currentTimeMillis();
        fast();
        System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
    }

    private static void fast()
    {
        StringBuilder s = new StringBuilder();
        for(int i=0;i<100000;i++)
            s.append("*");      
    }

    private static void slow()
    {
        String s = "";
        for(int i=0;i<100000;i++)
            s+="*";
    }
}

El resultado es:

slow elapsed 11741 ms
fast elapsed 7 ms

El problema es que + + añadir a una cadena reconstruye una nueva cadena, por lo que cuesta algo lineal a la longitud de sus cadenas (suma de ambas).

Entonces, a tu pregunta:

El segundo enfoque sería más rápido, pero es menos legible y más difícil de mantener. Como dije, en tu caso específico probablemente no verías la diferencia.


56
2017-10-07 15:58



También tuve un enfrentamiento con mi jefe sobre el hecho de usar append o +. Como están usando Append (todavía no puedo descifrar como dicen cada vez que se crea un nuevo objeto). Así que pensé en hacer algo de I + D. Aunque me encanta la explicación de Michael Borgwardt, solo quería mostrar una explicación si alguien realmente necesitaría saber en el futuro.

/**
 *
 * @author Perilbrain
 */
public class Appc {
    public Appc() {
        String x = "no name";
        x += "I have Added a name" + "We May need few more names" + Appc.this;
        x.concat(x);
        // x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
        //System.out.println(x);
    }

    public void Sb() {
        StringBuilder sbb = new StringBuilder("no name");
        sbb.append("I have Added a name");
        sbb.append("We May need few more names");
        sbb.append(Appc.this);
        sbb.append(sbb.toString());
        // System.out.println(sbb.toString());
    }
}

y el desmontaje de la clase anterior sale como

 .method public <init>()V //public Appc()
  .limit stack 2
  .limit locals 2
met001_begin:                                  ; DATA XREF: met001_slot000i
  .line 12
    aload_0 ; met001_slot000
    invokespecial java/lang/Object.<init>()V
  .line 13
    ldc "no name"
    astore_1 ; met001_slot001
  .line 14

met001_7:                                      ; DATA XREF: met001_slot001i
    new java/lang/StringBuilder //1st object of SB
    dup
    invokespecial java/lang/StringBuilder.<init>()V
    aload_1 ; met001_slot001
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    ldc "I have Added a nameWe May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    aload_0 ; met001_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    astore_1 ; met001_slot001
  .line 15
    aload_1 ; met001_slot001
    aload_1 ; met001_slot001
    invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
    pop
  .line 18
    return //no more SB created
met001_end:                                    ; DATA XREF: met001_slot000i ...

; ===========================================================================

;met001_slot000                                ; DATA XREF: <init>r ...
    .var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001                                ; DATA XREF: <init>+6w ...
    .var 1 is x Ljava/lang/String; from met001_7 to met001_end
  .end method
;44-1=44
; ---------------------------------------------------------------------------


; Segment type: Pure code
  .method public Sb()V //public void Sb
  .limit stack 3
  .limit locals 2
met002_begin:                                  ; DATA XREF: met002_slot000i
  .line 21
    new java/lang/StringBuilder
    dup
    ldc "no name"
    invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    astore_1 ; met002_slot001
  .line 22

met002_10:                                     ; DATA XREF: met002_slot001i
    aload_1 ; met002_slot001
    ldc "I have Added a name"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 23
    aload_1 ; met002_slot001
    ldc "We May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 24
    aload_1 ; met002_slot001
    aload_0 ; met002_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    pop
  .line 25
    aload_1 ; met002_slot001
    aload_1 ; met002_slot001
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 28
    return
met002_end:                                    ; DATA XREF: met002_slot000i ...


;met002_slot000                                ; DATA XREF: Sb+25r
    .var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001                                ; DATA XREF: Sb+9w ...
    .var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
  .end method
;96-49=48
; ---------------------------------------------------------------------------

De los dos códigos anteriores, puede ver que Michael tiene razón. En cada caso, solo se crea un objeto SB.


26
2017-12-21 20:29



Desde Java 1.5, la concatenación simple de una línea con "+" y StringBuilder.append () genera exactamente el mismo bytecode.

Entonces, por el bien de la legibilidad del código, use "+".

2 excepciones:

  • entorno multiproceso: StringBuffer
  • concatenación en bucles: StringBuilder / StringBuffer

23
2018-04-16 14:14



Usando la última versión de Java (1.8) el desmontaje (javap -c) muestra la optimización introducida por el compilador. + también sb.append() generará un código muy similar. Sin embargo, valdrá la pena inspeccionar el comportamiento si estamos usando + en un bucle for.

Agregar cadenas usando + en un bucle for

Java:

public String myCatPlus(String[] vals) {
    String result = "";
    for (String val : vals) {
        result = result + val;
    }
    return result;
}

ByteCode :(for extracto del bucle)

12: iload         5
14: iload         4
16: if_icmpge     51
19: aload_3
20: iload         5
22: aaload
23: astore        6
25: new           #3                  // class java/lang/StringBuilder
28: dup
29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload         6
38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc          5, 1
48: goto          12

Agregar cadenas usando stringbuilder.append

Java:

public String myCatSb(String[] vals) {
    StringBuilder sb = new StringBuilder();
    for(String val : vals) {
        sb.append(val);
    }
    return sb.toString();
}

ByteCdoe :(for extracto del bucle)

17: iload         5
19: iload         4
21: if_icmpge     43
24: aload_3
25: iload         5
27: aaload
28: astore        6
30: aload_2
31: aload         6
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc          5, 1
40: goto          17
43: aload_2

Hay un poco de diferencia flagrante aunque. En el primer caso, donde + fue utilizado, nuevo StringBuilder se crea para cada iteración de bucle y el resultado generado se almacena haciendo un toString() llamada (29 a 41). Entonces estás generando cadenas intermedias que realmente no necesitas mientras usas + operador en for lazo.


19
2018-05-26 07:15