StringBuilder vs concatenación de cadenas en toString () en Java

925

Dadas las 2 toString()implementaciones a continuación, cuál es la preferida:

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();
}

?

Más importante aún, dado que solo tenemos 3 propiedades, podría no hacer la diferencia, pero ¿en qué punto cambiaría de +concat a StringBuilder?

no secuestrador
fuente
40
¿En qué momento cambias a StringBuilder? Cuando afecta la memoria o el rendimiento. O cuando sea posible. Si realmente solo está haciendo esto por un par de cuerdas una vez, no se preocupe. Pero si va a hacerlo una y otra vez, debería ver una diferencia apreciable al usar StringBuilder.
ewall
¿Cuál es la media de 100 en el parámetro?
Asif Mushtaq
2
@UnKnown 100 es el tamaño inicial de StringBuilder
no sequitor
@nonsequitor ¿Entonces los caracteres máximos serán 100?
Asif Mushtaq
10
@Desconocido no solo el tamaño inicial, si conoce el tamaño aproximado de la cadena con la que está tratando, puede saber StringBuildercuánto tamaño asignar por adelantado, de lo contrario, si se queda sin espacio, tendrá que duplicar el tamaño creando un nueva char[]matriz luego copia los datos, lo cual es costoso. Puede hacer trampas dando el tamaño y luego no hay necesidad de esta creación de matriz, por lo que si cree que su cadena tendrá ~ 100 caracteres de largo, puede configurar el StringBuilder a ese tamaño y nunca tendrá que expandirse internamente.
no sequitor

Respuestas:

964

La versión 1 es preferible porque es más corta y , de hecho, el compilador la convertirá en la versión 2 , sin ninguna diferencia de rendimiento.

Más importante aún, dado que solo tenemos 3 propiedades, puede que no haga la diferencia, pero ¿en qué momento cambia de concat a constructor?

En el punto en el que estás concatenando en un bucle, generalmente es cuando el compilador no puede sustituirlo StringBuilderpor sí mismo.

Michael Borgwardt
fuente
19
Eso es cierto, pero la referencia del lenguaje también establece que esto es opcional. De hecho, acabo de hacer una prueba simple con JRE 1.6.0_15 y no vi ninguna optimización del compilador en la clase descompilada.
bruno conde
37
Acabo de probar el código de la pregunta (compilado en JDK 1.6.0_16) y encontré la optimización como se esperaba. Estoy bastante seguro de que todos los compiladores modernos lo harán.
Michael Borgwardt
22
Estás en lo correcto. Mirando el código de bytes, puedo ver claramente la optimización de StringBuilder. Estaba usando un descompilador y, de alguna manera, se está volviendo a convertir en concat. +1
bruno conde
80
No golpear a un caballo muerto, pero la redacción de la especificación es la siguiente: To increase the performance of repeated string concatenation, a Java compiler _may_ use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.La palabra clave que exista mayo . Dado que esto es oficialmente opcional (aunque probablemente implementado), ¿no deberíamos protegernos?
Lucas
93
@Lucas: No, no deberíamos. Si el compilador decide no realizar esa optimización, será porque no vale la pena. En el 99% de los casos, el compilador sabe mejor qué optimización vale la pena, por lo que, como regla general, el desarrollador no debe interferir. Por supuesto, su situación puede caer en el otro 1%, pero eso solo puede verificarse mediante una evaluación comparativa (cuidadosa).
sleske
255

La clave es si está escribiendo una concatenación única en un solo lugar o acumulándola con el tiempo.

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

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

Para aclarar, suponiendo que hugeArray contiene miles de cadenas, código como este:

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

consume mucho tiempo y memoria en comparación con:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();
joel.neely
fuente
3
Sí, StringBuilder no necesita volver a crear el objeto String una y otra vez.
Olga
124
Maldita sea, usé esas 2 funciones para probar una cadena grande en la que estoy trabajando. 6.51min vs 11secs
user1722791
1
Por cierto, también puede usar result += s;(en el primer ejemplo)
RAnders00
1
¿Cuántos objetos creará esta declaración? "{a:"+ a + ", b:" + b + ", c: " + c +"}";
Asif Mushtaq
1
¿Qué pasa con algo como: String str = (a == null)? nulo: a '+ (b == nulo)? nulo: b '+ (c == nulo)? c: c '+ ...; ? ¿Eso evitará que ocurra la optimización?
amitfr
75

Yo prefiero:

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

... porque es corto y legible.

Yo no optimizar este para la velocidad a menos que lo utilice dentro de un bucle con un alto número de repeticiones y de haber medido la diferencia de rendimiento.

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

tangenos
fuente
64
En realidad es más largo, contiene más símbolos y tiene variables un texto fuera de secuencia.
Tom Hawtin - tackline el
44
Entonces, ¿diría que es menos legible que uno de los otros enfoques?
tangens
3
Prefiero escribir esto, porque es más fácil agregar más variables, pero no estoy seguro de que sea más legible, especialmente a medida que aumenta el número de argumentos. Tampoco funciona en los momentos en que necesita agregar bits en diferentes momentos.
Alex Feinman el
78
Parece más difícil de leer (para mí). Ahora tengo que escanear de ida y vuelta entre {...} y los parámetros.
Steve Kuo
10
Prefiero este formulario, porque es seguro si uno de los parámetros esnull
rds
74

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 + = agregar a una cadena reconstruye una nueva cadena, por lo que cuesta algo lineal a la longitud de sus cadenas (suma de ambas).

Entonces, a su pregunta:

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

Omry Yadan
fuente
No te olvides de .concat (). Supongo que el tiempo transcurrido es de 10 a 18 ms, lo que lo hace insignificante cuando se usan cadenas cortas como el ejemplo de publicación original.
Droo
99
Si bien tiene razón +=, el ejemplo original era una secuencia de +que el compilador se transforma en una sola string.concatllamada. Sus resultados no aplican.
Blindy
1
@Blindy & Droo: - Ambos tienen razón. Usar .concate en tal escenario es la mejor solución, ya que + = crea un nuevo objeto cada vez que se ejecuta la rutina de bucle.
perilbrain
3
¿Sabes que su toString () no se llama en un bucle?
Omry Yadan
He intentado este ejemplo para probar la velocidad. Entonces mis resultados son: transcurrido lentamente 29672 ms; rápido transcurrido 15 ms. Entonces la respuesta es obvia. Pero si fueran 100 iteraciones, el tiempo es el mismo, 0 ms. Si 500 iteraciones - 16 ms y 0 ms. Y así.
Ernestas Gruodis
28

También tuve un conflicto con mi jefe sobre el hecho de si usar append o +. Como están usando Append (todavía no puedo entender como dicen cada vez que se crea un nuevo objeto). Así que pensé en hacer un poco de I + D. Aunque me encanta la explicación de Michael Borgwardt, pero solo quería mostrar una explicación si alguien realmente necesita 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.

perilbrain
fuente
27

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

Entonces, en aras de la legibilidad del código, use "+".

2 excepciones:

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

Usando la última versión de Java (1.8), el desensamblaje ( javap -c) muestra la optimización introducida por el compilador. +así sb.append()generará un código muy similar. Sin embargo, valdrá la pena inspeccionar el comportamiento si lo estamos usando +en un ciclo 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 :( forextracto 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 :( forextracto 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

Sin embargo, hay una diferencia notable. En el primer caso, donde +se usó, StringBuilderse crea nuevo para cada iteración de bucle y el resultado generado se almacena haciendo una toString()llamada (29 a 41). Por lo tanto, está generando cadenas intermedias que realmente no necesita mientras usa el +operador en forbucle.

portador del anillo
fuente
1
¿Es este Oracle JDK u OpenJDK?
Christophe Roussy
12

En Java 9, la versión 1 debería ser más rápida porque se convierte en invokedynamicllamada. Se pueden encontrar más detalles en JEP-280 :

La idea es reemplazar todo el baile de anexos StringBuilder con una simple llamada dinámica invocada a java.lang.invoke.StringConcatFactory, que aceptará los valores en la necesidad de concatenación.

ZhekaKozlov
fuente
9

Por razones de rendimiento, se desaconseja el uso de +=( Stringconcatenación). La razón es que: Java Stringes inmutable, cada vez que se realiza una nueva concatenación, se Stringcrea una nueva (la nueva tiene una huella digital diferente de la anterior que ya está en el conjunto de cadenas ). Crear nuevas cadenas ejerce presión sobre el GC y ralentiza el programa: la creación de objetos es costosa.

El siguiente código debería hacerlo más práctico y claro al mismo tiempo.

public static void main(String[] args) 
{
    // warming up
    for(int i = 0; i < 100; i++)
        RandomStringUtils.randomAlphanumeric(1024);
    final StringBuilder appender = new StringBuilder();
    for(int i = 0; i < 100; i++)
        appender.append(RandomStringUtils.randomAlphanumeric(i));

    // testing
    for(int i = 1; i <= 10000; i*=10)
        test(i);
}

public static void test(final int howMany) 
{
    List<String> samples = new ArrayList<>(howMany);
    for(int i = 0; i < howMany; i++)
        samples.add(RandomStringUtils.randomAlphabetic(128));

    final StringBuilder builder = new StringBuilder();
    long start = System.nanoTime();
    for(String sample: samples)
        builder.append(sample);
    builder.toString();
    long elapsed = System.nanoTime() - start;
    System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);

    String accumulator = "";
    start = System.nanoTime();
    for(String sample: samples)
        accumulator += sample;
    elapsed = System.nanoTime() - start;
    System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);

    start = System.nanoTime();
    String newOne = null;
    for(String sample: samples)
        newOne = new String(sample);
    elapsed = System.nanoTime() - start;
    System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}

Los resultados de una carrera se informan a continuación.

builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us

builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us

builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us

builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us

builder - 10000 - elapsed: 3364us 
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us

Sin considerar los resultados para 1 concatenación (JIT aún no estaba haciendo su trabajo), incluso para 10 concatenaciones la penalización de rendimiento es relevante; Para miles de concatenaciones, la diferencia es enorme.

Lecciones aprendidas de este experimento muy rápido (fácilmente reproducible con el código anterior): nunca use el +=para concatenar cadenas juntas, incluso en casos muy básicos donde se necesitan algunas concatenaciones (como se dijo, crear nuevas cadenas es costoso de todos modos y ejerce presión sobre el GC).

Paolo Maresca
fuente
9

Vea el siguiente ejemplo:

static final int MAX_ITERATIONS = 50000;
static final int CALC_AVG_EVERY = 10000;

public static void main(String[] args) {
    printBytecodeVersion();
    printJavaVersion();
    case1();//str.concat
    case2();//+=
    case3();//StringBuilder
}

static void case1() {
    System.out.println("[str1.concat(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str = str.concat(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case2() {
    System.out.println("[str1+=str2]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str += UUID.randomUUID() + "---";
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case3() {
    System.out.println("[str1.append(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    StringBuilder str = new StringBuilder("");
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str.append(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");

}

static void saveTime(List<Long> executionTimes, long startTime) {
    executionTimes.add(System.currentTimeMillis() - startTime);
    if (executionTimes.size() % CALC_AVG_EVERY == 0) {
        out.println("average time for " + executionTimes.size() + " concatenations: "
                + NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(() -> 0))
                + " ms avg");
        executionTimes.clear();
    }
}

Salida:

java bytecode versión: 8
java.version: 1.8.0_144
[str1.concat (str2)]
tiempo promedio para 10000 concatenaciones: 0.096 ms
tiempo promedio promedio para 10000 concatenaciones: 0.185 ms
tiempo promedio promedio para 10000 concatenaciones: 0.327 ms
tiempo promedio promedio para 10000 concatenaciones: 0.501 ms de
tiempo promedio promedio para 10000 concatenaciones: 0.656 ms de promedio
Cadena de longitud creada: 1950000 en 17745 ms
[str1 + = str2]
tiempo promedio para 10000 concatenaciones: 0.21 ms promedio de
tiempo promedio para 10000 concatenaciones: 0.652 ms promedio de
tiempo promedio para 10000 concatenaciones: 1.129 ms promedio de
tiempo promedio para 10000 concatenaciones: 1.727 ms promedio
tiempo promedio para 10000 concatenaciones: promedio de 2.302 ms
Cadena de longitud creada: 1950000 en 60279 ms
[str1.append (str2)]
tiempo promedio para 10000 concatenaciones: 0.002 ms promedio de
tiempo promedio para 10000 concatenaciones: 0.002 ms
tiempo promedio promedio para 10000 concatenaciones:
Promedio de 0.002 ms de tiempo promedio para 10000 concatenaciones: 0.002 ms de promedio de
tiempo promedio para 10000 concatenaciones: 0.002 ms de promedio
Cadena de longitud creada: 1950000 en 100 ms

A medida que aumenta la longitud de la cuerda, también lo hace el tiempo de concatenación.
Ahí es donde StringBuilderdefinitivamente se necesita.
Como puede ver, la concatenación: UUID.randomUUID()+"---"realmente no afecta el tiempo.

PD: No creo que usar StringBuilder en Java sea ​​realmente un duplicado de esto.
Esta pregunta habla de toString()que la mayoría de las veces no realiza concatenaciones de cadenas enormes.


Actualización 2019

Desde java8tiempos, las cosas han cambiado un poco. Parece que ahora (java13), el tiempo de concatenación de +=es prácticamente el mismo que str.concat(). Sin embargo, el StringBuildertiempo de concatenación sigue siendo constante . (La publicación original anterior fue ligeramente editada para agregar una salida más detallada)

java bytecode versión: 13
java.version: 13.0.1
[str1.concat (str2)]
tiempo promedio para 10000 concatenaciones: 0.047 ms promedio de
tiempo promedio para 10000 concatenaciones: 0.1 ms promedio de
tiempo promedio para 10000 concatenaciones: 0.17 ms promedio de
tiempo promedio para 10.000 concatenaciones: 0.255 ms de
tiempo promedio promedio para 10000 concatenaciones: 0.336 ms de promedio
Cadena de longitud creada: 1950000 en 9147 ms
[str1 + = str2]
tiempo promedio para 10000 concatenaciones: 0.037 ms promedio de
tiempo promedio para 10000 concatenaciones: 0.097 ms promedio de
tiempo promedio para 10000 concatenaciones: 0.249 ms promedio de
tiempo promedio para 10000 concatenaciones: 0.298 ms promedio
tiempo promedio para 10000 concatenaciones: 0.326 ms promedio
Cadena de longitud creada: 1950000 en 10191 ms
[str1.append (str2)]
tiempo promedio para 10000 concatenaciones: 0.001 ms
tiempo promedio promedio para 10000 concatenaciones: 0.001 ms
tiempo promedio promedio para 10000 concatenaciones: 0.001 ms
tiempo promedio promedio para 10000 concatenaciones:
Promedio de 0.001 ms de tiempo promedio para 10000 concatenaciones: 0.001 ms de promedio
Cadena de longitud creada: 1950000 en 43 ms

Vale la pena señalar que también la bytecode:8/java.version:13combinación tiene un buen rendimiento en comparación conbytecode:8/java.version:8

Marinos An
fuente
Esta debería ser la respuesta aceptada ... depende del tamaño de String Stream que determina la elección de concat o StringBuilder
user1428716
@ user1428716 FYI: respuesta actualizada con resultados java13que ahora son diferentes. Pero creo que la conclusión principal sigue siendo la misma.
Marinos An
7

Apache Commons-Lang tiene una clase ToStringBuilder que es súper fácil de usar. Hace un buen trabajo al manejar la lógica de apéndice y al formatear cómo desea que se vea su toString.

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}

Devolverá la salida que se parece com.blah.YourClass@abc1321f[a=whatever, b=foo].

O en una forma más condensada usando encadenamiento:

public void toString() {
     return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}

O si desea utilizar la reflexión para incluir todos los campos de la clase:

public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

También puede personalizar el estilo de ToString si lo desea.

mate b
fuente
4

¡Haga que el método toString sea lo más legible posible!

La única excepción para esto en mi libro es si puedes demostrarme que consume recursos significativos :) (Sí, esto significa perfilar)

También tenga en cuenta que el compilador Java 5 genera un código más rápido que el enfoque "StringBuffer" escrito a mano utilizado en versiones anteriores de Java. Si usa "+", esta y futuras mejoras son gratuitas.

Thorbjørn Ravn Andersen
fuente
3

Parece haber cierto debate sobre si aún se necesita usar StringBuilder con los compiladores actuales. Así que pensé en dar mis 2 centavos de experiencia.

Tengo un JDBCconjunto de resultados de 10k registros (sí, los necesito todos en un lote). El uso del operador + lleva aproximadamente 5 minutos en mi máquina Java 1.8. Usar stringBuilder.append("")toma menos de un segundo para la misma consulta.

Entonces la diferencia es enorme. Dentro de un bucle StringBuilderes mucho más rápido.

Remolino
fuente
2
Creo que el debate se trata de usarlo fuera de los bucles. Creo que hay un consenso que necesitas usar dentro de un bucle.
Jordan
2

La concatenación de cadenas sabia de rendimiento usando '+' es más costosa porque tiene que hacer una copia completamente nueva de la cadena ya que las cadenas son inmutables en Java. Esto juega un papel particular si la concatenación es muy frecuente, por ejemplo: dentro de un bucle. Lo siguiente es lo que sugiere mi IDEA cuando intento hacer algo así:

ingrese la descripción de la imagen aquí

Reglas generales:

  • Dentro de una sola asignación de cadena, usar la concatenación de cadenas está bien.
  • Si está haciendo un bucle para construir un gran bloque de datos de caracteres, vaya a StringBuffer.
  • Usar + = en un String siempre será menos eficiente que usar un StringBuffer, por lo que debería sonar las campanas de advertencia, pero en ciertos casos la optimización obtenida será insignificante en comparación con los problemas de legibilidad, así que use su sentido común.

Aquí hay un buen blog de Jon Skeet sobre este tema.

Sudip Bhandari
fuente
2
Nunca debe usar StringBuffer a menos que requiera absolutamente acceso sincronizado desde múltiples hilos. De lo contrario, prefiera StringBuilder, que no está sincronizado y, por lo tanto, tiene menos sobrecarga.
Erki der Loony
1

¿Puedo señalar que si va a iterar sobre una colección y utilizar StringBuilder, puede consultar Apache Commons Lang y StringUtils.join () (en diferentes sabores)?

Independientemente del rendimiento, le ahorrará tener que crear StringBuilders y bucles por lo que parece ser la millonésima vez.

Brian Agnew
fuente
1

Esto es lo que verifiqué en Java8

  • Usando la concatenación de cadenas
  • Usando StringBuilder

    long time1 = System.currentTimeMillis();
    usingStringConcatenation(100000);
    System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms");
    
    time1 = System.currentTimeMillis();
    usingStringBuilder(100000);
    System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms");
    
    
    private static void usingStringBuilder(int n)
    {
        StringBuilder str = new StringBuilder();
        for(int i=0;i<n;i++)
            str.append("myBigString");    
    }
    
    private static void usingStringConcatenation(int n)
    {
        String str = "";
        for(int i=0;i<n;i++)
            str+="myBigString";
    }

Es realmente una pesadilla si está utilizando la concatenación de cadenas para una gran cantidad de cadenas.

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms
nagendra547
fuente
-1

Creo que deberíamos ir con el enfoque de agregar StringBuilder. Razón de ser :

  1. La cadena concatenada creará un nuevo objeto de cadena cada vez (como la cadena es un objeto inmutable), por lo que creará 3 objetos.

  2. Con el generador de cadenas solo se creará un objeto [StringBuilder es mutable] y la cadena adicional se agregará a él.

VishalTambe
fuente
¿Por qué esta respuesta es rechazada? docs.oracle.com/javase/8/docs/api/java/util/stream/… - Reducción
mutable
-4

Para cadenas simples como esa prefiero usar

"string".concat("string").concat("string");

En orden, diría que el método preferido para construir una cadena es usar StringBuilder, String # concat (), luego el operador sobrecargado +. StringBuilder es un aumento significativo del rendimiento cuando se trabaja con cadenas grandes al igual que el uso del operador + es una gran disminución en el rendimiento (disminución exponencialmente grande a medida que aumenta el tamaño de la cadena). El único problema con el uso de .concat () es que puede arrojar NullPointerExceptions.

Droo
fuente
99
Es probable que el uso de concat () tenga un rendimiento peor que '+' ya que JLS permite que '+' se convierta en un StringBuilder, y lo más probable es que todos los JVM lo hagan o utilicen una alternativa más eficiente; no es probable que lo mismo sea cierto para concat que debe crear y descartar al menos una cadena intermedia completa en su ejemplo.
Lawrence Dol