Pregunta

Suponiendo las cadenas a y b:

a += b
a = a.concat(b)

Bajo el capó, ¿son lo mismo?

Aquí está concat descompilado como referencia.Me gustaría poder descompilar el + operador también para ver qué hace.

public String concat(String s) {

    int i = s.length();
    if (i == 0) {
        return this;
    }
    else {
        char ac[] = new char[count + i];
        getChars(0, count, ac, 0);
        s.getChars(0, i, ac, count);
        return new String(0, count + i, ac);
    }
}
¿Fue útil?

Solución

No, no del todo.

En primer lugar, hay una ligera diferencia en la semántica.Si a es null, entonces a.concat(b) lanza un NullPointerException pero a+=b tratará el valor original de a como si fuera null.Además, el concat() El método solo acepta String valores mientras que el + El operador convertirá silenciosamente el argumento en una cadena (usando el toString() método para objetos).Entonces el concat() El método es más estricto en lo que acepta.

Para mirar debajo del capó, escriba una clase simple con a += b;

public class Concat {
    String cat(String a, String b) {
        a += b;
        return a;
    }
}

Ahora desmonte con javap -c (incluido en el Sun JDK).Debería ver una lista que incluya:

java.lang.String cat(java.lang.String, java.lang.String);
  Code:
   0:   new     #2; //class java/lang/StringBuilder
   3:   dup
   4:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   7:   aload_1
   8:   invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   11:  aload_2
   12:  invokevirtual   #4; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   15:  invokevirtual   #5; //Method java/lang/StringBuilder.toString:()Ljava/lang/    String;
   18:  astore_1
   19:  aload_1
   20:  areturn

Entonces, a += b es el equivalente de

a = new StringBuilder()
    .append(a)
    .append(b)
    .toString();

El concat El método debería ser más rápido.Sin embargo, con más cuerdas el StringBuilder El método gana, al menos en términos de rendimiento.

El código fuente de String y StringBuilder (y su clase base de paquete privado) está disponible en src.zip del Sun JDK.Puede ver que está creando una matriz de caracteres (cambiando el tamaño según sea necesario) y luego desechándola cuando crea la versión final. String.En la práctica, la asignación de memoria es sorprendentemente rápida.

Actualizar: Como señala Pawel Adamski, el rendimiento ha cambiado en HotSpot más reciente. javac todavía produce exactamente el mismo código, pero el compilador de código de bytes hace trampa.Las pruebas simples fallan por completo porque se desecha todo el cuerpo del código.sumando System.identityHashCode (no String.hashCode) muestra el StringBuffer El código tiene una ligera ventaja.Sujeto a cambios cuando se publique la próxima actualización o si utiliza una JVM diferente.De @lukaseder, una lista de elementos intrínsecos de HotSpot JVM.

Otros consejos

niyaz es correcto, pero también vale la pena señalar que el compilador de Java puede convertir el operador + especial en algo más eficiente.Java tiene una clase StringBuilder que representa una cadena mutable y no segura para subprocesos.Al realizar un montón de concatenaciones de cadenas, el compilador de Java convierte silenciosamente

String a = b + c + d;

en

String a = new StringBuilder(b).append(c).append(d).toString();

que para cadenas grandes es significativamente más eficiente.Hasta donde yo sé, esto no sucede cuando usas el método concat.

Sin embargo, el método concat es más eficiente cuando se concatena una cadena vacía en una cadena existente.En este caso, la JVM no necesita crear un nuevo objeto String y puede simplemente devolver el existente.Ver la documentación concat para confirmar esto.

Entonces, si está muy preocupado por la eficiencia, entonces debe usar el método concat al concatenar cadenas posiblemente vacías y usar + en caso contrario.Sin embargo, la diferencia de rendimiento debería ser insignificante y probablemente nunca deberías preocuparte por esto.

Ejecuté una prueba similar a la de @marcio pero con el siguiente bucle:

String c = a;
for (long i = 0; i < 100000L; i++) {
    c = c.concat(b); // make sure javac cannot skip the loop
    // using c += b for the alternative
}

Sólo por si acaso, agregué StringBuilder.append() también.Cada prueba se realizó 10 veces, con 100.000 repeticiones por cada carrera.Aquí están los resultados:

  • StringBuilder gana sin lugar a dudas.El resultado del tiempo de reloj fue 0 para la mayoría de las ejecuciones, y la más larga tomó 16 ms.
  • a += b tarda aproximadamente 40000 ms (40 s) por cada ejecución.
  • concat solo requiere 10000 ms (10 s) por ejecución.

Todavía no he descompilado la clase para ver las partes internas ni ejecutarla a través del generador de perfiles, pero sospecho que a += b pasa gran parte del tiempo creando nuevos objetos de StringBuilder y luego convertirlos nuevamente a String.

La mayoría de las respuestas aquí son de 2008.Parece que las cosas han cambiado con el tiempo.Mis últimos puntos de referencia realizados con JMH muestran que en Java 8 + es aproximadamente dos veces más rápido que concat.

Mi punto de referencia:

@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
public class StringConcatenation {

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State2 {
        public String a = "abc";
        public String b = "xyz";
    }

    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State3 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
    }


    @org.openjdk.jmh.annotations.State(Scope.Thread)
    public static class State4 {
        public String a = "abc";
        public String b = "xyz";
        public String c = "123";
        public String d = "!@#";
    }

    @Benchmark
    public void plus_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b);
    }

    @Benchmark
    public void plus_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c);
    }

    @Benchmark
    public void plus_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a+state.b+state.c+state.d);
    }

    @Benchmark
    public void stringbuilder_2(State2 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).toString());
    }

    @Benchmark
    public void stringbuilder_3(State3 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).toString());
    }

    @Benchmark
    public void stringbuilder_4(State4 state, Blackhole blackhole) {
        blackhole.consume(new StringBuilder().append(state.a).append(state.b).append(state.c).append(state.d).toString());
    }

    @Benchmark
    public void concat_2(State2 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b));
    }

    @Benchmark
    public void concat_3(State3 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c)));
    }


    @Benchmark
    public void concat_4(State4 state, Blackhole blackhole) {
        blackhole.consume(state.a.concat(state.b.concat(state.c.concat(state.d))));
    }
}

Resultados:

Benchmark                             Mode  Cnt         Score         Error  Units
StringConcatenation.concat_2         thrpt   50  24908871.258 ± 1011269.986  ops/s
StringConcatenation.concat_3         thrpt   50  14228193.918 ±  466892.616  ops/s
StringConcatenation.concat_4         thrpt   50   9845069.776 ±  350532.591  ops/s
StringConcatenation.plus_2           thrpt   50  38999662.292 ± 8107397.316  ops/s
StringConcatenation.plus_3           thrpt   50  34985722.222 ± 5442660.250  ops/s
StringConcatenation.plus_4           thrpt   50  31910376.337 ± 2861001.162  ops/s
StringConcatenation.stringbuilder_2  thrpt   50  40472888.230 ± 9011210.632  ops/s
StringConcatenation.stringbuilder_3  thrpt   50  33902151.616 ± 5449026.680  ops/s
StringConcatenation.stringbuilder_4  thrpt   50  29220479.267 ± 3435315.681  ops/s

Tom tiene razón al describir exactamente lo que hace el operador +.Crea un temporal StringBuilder, añade las partes y termina con toString().

Sin embargo, todas las respuestas hasta ahora ignoran los efectos de las optimizaciones del tiempo de ejecución de HotSpot.Específicamente, estas operaciones temporales se reconocen como un patrón común y se reemplazan con código de máquina más eficiente en tiempo de ejecución.

@marcio:Has creado un micro-punto de referencia;Con las JVM modernas, esta no es una forma válida de generar perfiles de código.

La razón por la que la optimización en tiempo de ejecución es importante es que muchas de estas diferencias en el código (incluso incluida la creación de objetos) son completamente diferentes una vez que HotSpot se pone en marcha.La única forma de saberlo con seguridad es perfilando su código. en el lugar.

Finalmente, todos estos métodos son increíblemente rápidos.Este podría ser un caso de optimización prematura.Si tiene código que concatena muchas cadenas, la forma de obtener la velocidad máxima probablemente no tenga nada que ver con los operadores que elija sino con el algoritmo que esté utilizando.

¿Qué tal algunas pruebas sencillas?Usé el siguiente código:

long start = System.currentTimeMillis();

String a = "a";

String b = "b";

for (int i = 0; i < 10000000; i++) { //ten million times
     String c = a.concat(b);
}

long end = System.currentTimeMillis();

System.out.println(end - start);
  • El "a + b" versión ejecutada en 2500ms.
  • El a.concat(b) ejecutado en 1200ms.

Probado varias veces.El concat() La ejecución de la versión tomó la mitad del tiempo en promedio.

Este resultado me sorprendió porque el concat() El método siempre crea una nueva cadena (devuelve un "new String(result)".Es bien sabido que:

String a = new String("a") // more than 20 times slower than String a = "a"

¿Por qué el compilador no fue capaz de optimizar la creación de cadenas en código "a + b", sabiendo que siempre daba como resultado la misma cadena?Podría evitar la creación de una nueva cadena.Si no cree en la afirmación anterior, compruébelo usted mismo.

Básicamente, hay dos diferencias importantes entre + y el concat método.

  1. Si estás usando el concat método, entonces solo podrá concatenar cadenas mientras que en el caso del + operador, también puede concatenar la cadena con cualquier tipo de datos.

    Por ejemplo:

    String s = 10 + "Hello";
    

    En este caso, la salida debe ser 10Hola.

    String s = "I";
    String s1 = s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    En el caso anterior, debe proporcionar dos cadenas de forma obligatoria.

  2. La segunda y principal diferencia entre + y concat es eso:

    Caso 1:Supongamos que concateno las mismas cadenas con concat operador de esta manera

    String s="I";
    String s1=s.concat("am").concat("good").concat("boy");
    System.out.println(s1);
    

    En este caso, el número total de objetos creados en el grupo es 7, como este:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy
    

    Caso 2:

    Ahora voy a concatenar las mismas cadenas mediante + operador

    String s="I"+"am"+"good"+"boy";
    System.out.println(s);
    

    En el caso anterior, el número total de objetos creados es solo 5.

    En realidad, cuando concatenamos las cadenas a través de + operador luego mantiene una clase StringBuffer para realizar la misma tarea de la siguiente manera: -

    StringBuffer sb = new StringBuffer("I");
    sb.append("am");
    sb.append("good");
    sb.append("boy");
    System.out.println(sb);
    

    De esta forma creará sólo cinco objetos.

Chicos, estas son las diferencias básicas entre + y el concat método.Disfrutar :)

Para completar, quería agregar que la definición del operador '+' se puede encontrar en el JLS SE8 15.18.1:

Si solo una expresión de operando es de Type String, entonces la conversión de cadena (§5.1.11) se realiza en el otro operando para producir una cadena en el tiempo de ejecución.

El resultado de la concatenación de cadenas es una referencia a un objeto de cadena que es la concatenación de las dos cadenas de operando.Los caracteres del operando izquierdo preceden a los caracteres del operando de la derecha en la cadena recién creada.

El objeto de cadena se crea recientemente (§12.5) a menos que la expresión sea una expresión constante (§15.28).

Sobre la implementación el JLS dice lo siguiente:

Una implementación puede optar por realizar la conversión y la concatenación en un paso para evitar crear y luego descartar un objeto de cadena intermedio.Para aumentar el rendimiento de la concatenación de cadenas repetidas, un compilador Java puede usar la clase StringBuffer o una técnica similar para reducir el número de objetos de cadena intermedios que se crean mediante la evaluación de una expresión.

Para los tipos primitivos, una implementación también puede optimizar la creación de un objeto de envoltura al convertir directamente de un tipo primitivo a una cadena.

Entonces, a juzgar por "un compilador de Java puede usar la clase StringBuffer o una técnica similar para reducir", diferentes compiladores podrían producir diferentes códigos de bytes.

El + operador puede funcionar entre una cadena y un valor de tipo de datos cadena, char, entero, doble o flotante.Simplemente convierte el valor a su representación de cadena antes de la concatenación.

El operador concat Sólo se puede hacer sobre y con cuerdas.Comprueba la compatibilidad del tipo de datos y arroja un error si no coinciden.

Excepto esto, el código que proporcionaste hace lo mismo.

No me parece.

a.concat(b) está implementado en String y creo que la implementación no cambió mucho desde las primeras máquinas Java.El + La implementación de la operación depende de la versión de Java y del compilador.Actualmente + se implementa usando StringBuffer para que la operación sea lo más rápida posible.Quizás en el futuro esto cambie.En versiones anteriores de java + La operación en Strings fue mucho más lenta ya que produjo resultados intermedios.

Supongo += se implementa usando + y optimizado de manera similar.

Cuando se usa +, la velocidad disminuye a medida que aumenta la longitud de la cadena, pero cuando se usa concat, la velocidad es más estable y la mejor opción es usar la clase StringBuilder que tiene una velocidad estable para poder hacerlo.

Supongo que puedes entender por qué.Pero la mejor manera de crear cadenas largas es usar StringBuilder() y append(); cualquiera de las dos velocidades será inaceptable.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top