¿Es una mejor práctica usar String.format sobre Concatenación de cadenas en Java?

273

¿Hay una diferencia perceptible entre el uso String.formaty la concatenación de cadenas en Java?

Tiendo a usar, String.formatpero ocasionalmente me resbalaré y usaré una concatenación. Me preguntaba si uno era mejor que el otro.

A mi modo de ver, String.formatte da más poder para "formatear" la cadena; y la concatenación significa que no tiene que preocuparse por poner accidentalmente un% so extra o perder uno.

String.format También es más corto.

Cuál es más legible depende de cómo funcione su cabeza.

Omar Kooheji
fuente
Creo que podemos ir con MessageFormat.format. Consulte la respuesta stackoverflow.com/a/56377112/1491414 para obtener más información.
Ganesa Vijayakumar

Respuestas:

242

Sugeriría que es una mejor práctica usar String.format(). La razón principal es que String.format()puede localizarse más fácilmente con texto cargado de archivos de recursos, mientras que la concatenación no puede localizarse sin producir un nuevo ejecutable con un código diferente para cada idioma.

Si planea que su aplicación sea localizable, también debe acostumbrarse a especificar posiciones de argumento para sus tokens de formato:

"Hello %1$s the time is %2$t"

Esto se puede localizar y cambiar los tokens de nombre y tiempo sin requerir una recompilación del ejecutable para dar cuenta de los diferentes pedidos. Con las posiciones de argumento también puede reutilizar el mismo argumento sin pasarlo dos veces a la función:

String.format("Hello %1$s, your name is %1$s and the time is %2$t", name, time)
workmad3
fuente
1
¿Puede señalarme alguna documentación que habla sobre cómo trabajar con posiciones / orden de argumentos en Java (es decir, cómo hacer referencia a los argumentos por su posición)? Gracias.
markvgti
13
Mejor tarde que nunca, versión aleatoria de Java: docs.oracle.com/javase/1.5.0/docs/api/java/util/…
Aksel
174

Sobre el rendimiento:

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }
  long end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;

  start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = String.format("Hi %s; Hi to you %s",i, + i*2);
  }
  end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
}

Los resultados del tiempo son los siguientes:

  • Concatenación = 265 milisegundos
  • Formato = 4141 milisegundos

Por lo tanto, la concatenación es mucho más rápida que String.format.

Icaro
fuente
15
Todos son malas prácticas. Usa StringBuilder.
Amir Raminfar
8
StringBuilder está fuera de alcance aquí (la pregunta de OP era sobre comparar String.format sobre String Concatenation) pero ¿ha realizado datos sobre String Builder?
Icaro
108
@AmirRaminar: el compilador convierte "+" en llamadas a StringBuilder automáticamente.
Martin Schröder
40
@ MartinSchröder: Si ejecuta javap -c StringTest.class, verá que el compilador convierte "+" a StringBuilder automáticamente solo si no está en un bucle. Si la concatenación se realiza en una sola línea, es lo mismo que usar '+', pero si usa myString += "morechars";o myString += anotherString;en varias líneas, notará que se puede crear más de un StringBuilder, por lo que usar "+" no siempre es tan eficiente como StringBuilder.
ccpizza
77
@Joffrey: lo que quise decir es que para los bucles +no se convierte, StringBuilder.append()sino que new StringBuilder()sucede en cada iteración.
ccpizza
39

Como hay una discusión sobre el rendimiento, pensé que agregaría una comparación que incluyera StringBuilder. De hecho, es más rápido que el concat y, naturalmente, la opción String.format.

Para hacer de esto una especie de comparación de manzanas con manzanas, ejemplifico un nuevo StringBuilder en el bucle en lugar de en el exterior (en realidad, esto es más rápido que hacer una sola instancia, muy probablemente debido a la sobrecarga de reasignación de espacio para el bucle anexar al final de Un constructor).

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    log.info("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    log.info("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("; Hi to you ").append(i * 2);
    }

    end = System.currentTimeMillis();

    log.info("String Builder = " + ((end - start)) + " millisecond");
  • 2012-01-11 16: 30: 46,058 INFO [TestMain] - Formato = 1416 milisegundos
  • 2012-01-11 16: 30: 46,190 INFO [TestMain] - Concatenación = 134 milisegundos
  • 2012-01-11 16: 30: 46,313 INFO [TestMain] - Generador de cadenas = 117 milisegundos
TechTrip
fuente
21
La prueba StringBuilder no llama a toString (), por lo que no es una comparación justa. Sospecho que encontrará que está dentro del error de medición del rendimiento de la concatenación si corrige ese error.
Jamey Sharp
15
En las pruebas de concatenación y formato, solicitó a String. La prueba de StringBuilder, para ser justos, necesita un paso final que convierta el contenido de StringBuilder en una cadena. Haces eso llamando bldString.toString(). ¿Espero que esto lo explique?
Jamey Sharp
44
Jamey Sharp tiene toda la razón. Invocar bldString.toString () es casi lo mismo, si no más lento, que la concatenación de cadenas.
Akos Cz
3
String s = bldString.toString(); Los tiempos eran con la concatenación y StringBuilder casi a la par con los demás: Format = 1520 millisecond, Concatenation = 167 millisecond, String Builder = 173 millisecond me encontré con ellos en un bucle y un promedio de cada uno hacia fuera para conseguir un buen representante: (optimización de pre-JVM, intentará un bucle 10000+ cuando llegue el tiempo)
TechTrip
3
¿Cómo saben ustedes si el código se ejecuta en absoluto? Las variables nunca se leen o usan, no puede estar seguro de que JIT no elimine este código en primer lugar.
alobodzk
37

Un problema .formates que pierde la seguridad de tipo estático. Puede tener muy pocos argumentos para su formato, y puede tener los tipos incorrectos para los especificadores de formato, ambos conducen a un IllegalFormatException tiempo de ejecución , por lo que podría terminar con un código de registro que interrumpe la producción.

Por el contrario, los argumentos +pueden ser probados por el compilador.

La historia de seguridad de(en el que formatse modela la función) es largo y aterrador.

Martin Schröder
fuente
16
solo para que conste, los IDEs modernos (por ejemplo, IntelliJ) ayudan en el conteo de argumentos y la correspondencia de tipos
Ron Klein
2
Buen punto sobre la compilación, le recomiendo que haga estas comprobaciones a través de FindBugs (que puede ejecutarse en el IDE o Maven durante la compilación), tenga en cuenta que esto también verificará el formato en todos sus registros. Esto funciona independientemente de los usuarios IDE
Christophe Roussy
20

Cuál es más legible depende de cómo funcione su cabeza.

Tienes tu respuesta allí mismo.

Es una cuestión de gusto personal.

La concatenación de cadenas es marginalmente más rápida, supongo, pero eso debería ser insignificante.

Thilo
fuente
3
Estoy de acuerdo. Pensar en las diferencias de rendimiento aquí es principalmente una optimización prematura: en el improbable caso de que el perfil muestre que hay un problema aquí, entonces preocúpese.
Jonik
3
Solo es realmente una cuestión de gusto personal si el proyecto es pequeño y nunca tuvo la intención de internacionalizarse en ningún sentido significativo. De lo contrario, String.format gana a la concatenación en todos los sentidos.
workmad3
44
Estoy en desacuerdo. No importa cuán grande sea el proyecto, difícilmente podrá localizar cada cadena que se haya construido dentro de él. En otras palabras, depende de la situación (para qué se utilizan las cadenas).
Jonik
No puedo imaginar cómo alguien consideraría 'String.format ("% s% s", a, b)' más legible que 'a + b', y dada la diferencia de velocidad en el orden de magnitud La respuesta me parece clara (en situaciones que no requieren localización, como la depuración o la mayoría de las declaraciones de registro).
BobDoolittle
16

Aquí hay una prueba con múltiples tamaños de muestra en milisegundos.

public class Time {

public static String sysFile = "/sys/class/camera/rear/rear_flash";
public static String cmdString = "echo %s > " + sysFile;

public static void main(String[] args) {

  int i = 1;
  for(int run=1; run <= 12; run++){
      for(int test =1; test <= 2 ; test++){
        System.out.println(
                String.format("\nTEST: %s, RUN: %s, Iterations: %s",run,test,i));
        test(run, i);
      }
      System.out.println("\n____________________________");
      i = i*3;
  }
}

public static void test(int run, int iterations){

      long start = System.nanoTime();
      for( int i=0;i<iterations; i++){
          String s = "echo " + i + " > "+ sysFile;
      }
      long t = System.nanoTime() - start;   
      String r = String.format("  %-13s =%10d %s", "Concatenation",t,"nanosecond");
      System.out.println(r) ;


     start = System.nanoTime();       
     for( int i=0;i<iterations; i++){
         String s =  String.format(cmdString, i);
     }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "Format",t,"nanosecond");
     System.out.println(r);

      start = System.nanoTime();          
      for( int i=0;i<iterations; i++){
          StringBuilder b = new StringBuilder("echo ");
          b.append(i).append(" > ").append(sysFile);
          String s = b.toString();
      }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "StringBuilder",t,"nanosecond");
     System.out.println(r);
}

}

TEST: 1, RUN: 1, Iterations: 1
  Concatenation =     14911 nanosecond
  Format        =     45026 nanosecond
  StringBuilder =      3509 nanosecond

TEST: 1, RUN: 2, Iterations: 1
  Concatenation =      3509 nanosecond
  Format        =     38594 nanosecond
  StringBuilder =      3509 nanosecond

____________________________

TEST: 2, RUN: 1, Iterations: 3
  Concatenation =      8479 nanosecond
  Format        =     94438 nanosecond
  StringBuilder =      5263 nanosecond

TEST: 2, RUN: 2, Iterations: 3
  Concatenation =      4970 nanosecond
  Format        =     92976 nanosecond
  StringBuilder =      5848 nanosecond

____________________________

TEST: 3, RUN: 1, Iterations: 9
  Concatenation =     11403 nanosecond
  Format        =    287115 nanosecond
  StringBuilder =     14326 nanosecond

TEST: 3, RUN: 2, Iterations: 9
  Concatenation =     12280 nanosecond
  Format        =    209051 nanosecond
  StringBuilder =     11818 nanosecond

____________________________

TEST: 5, RUN: 1, Iterations: 81
  Concatenation =     54383 nanosecond
  Format        =   1503113 nanosecond
  StringBuilder =     40056 nanosecond

TEST: 5, RUN: 2, Iterations: 81
  Concatenation =     44149 nanosecond
  Format        =   1264241 nanosecond
  StringBuilder =     34208 nanosecond

____________________________

TEST: 6, RUN: 1, Iterations: 243
  Concatenation =     76018 nanosecond
  Format        =   3210891 nanosecond
  StringBuilder =     76603 nanosecond

TEST: 6, RUN: 2, Iterations: 243
  Concatenation =     91222 nanosecond
  Format        =   2716773 nanosecond
  StringBuilder =     73972 nanosecond

____________________________

TEST: 8, RUN: 1, Iterations: 2187
  Concatenation =    527450 nanosecond
  Format        =  10291108 nanosecond
  StringBuilder =    885027 nanosecond

TEST: 8, RUN: 2, Iterations: 2187
  Concatenation =    526865 nanosecond
  Format        =   6294307 nanosecond
  StringBuilder =    591773 nanosecond

____________________________

TEST: 10, RUN: 1, Iterations: 19683
  Concatenation =   4592961 nanosecond
  Format        =  60114307 nanosecond
  StringBuilder =   2129387 nanosecond

TEST: 10, RUN: 2, Iterations: 19683
  Concatenation =   1850166 nanosecond
  Format        =  35940524 nanosecond
  StringBuilder =   1885544 nanosecond

  ____________________________

TEST: 12, RUN: 1, Iterations: 177147
  Concatenation =  26847286 nanosecond
  Format        = 126332877 nanosecond
  StringBuilder =  17578914 nanosecond

TEST: 12, RUN: 2, Iterations: 177147
  Concatenation =  24405056 nanosecond
  Format        = 129707207 nanosecond
  StringBuilder =  12253840 nanosecond
Derek Ziemba
fuente
1
StringBuilder es absolutamente el método más rápido cuando agrega caracteres en un bucle, por ejemplo, cuando desea crear una cadena con mil 1s agregándolos uno por uno. Aquí hay más información: pellegrino.link/2015/08/22/…
Carlos Hoyos
Me gusta cómo siempre usas String.format para la salida: D, por lo que hay una ventaja. y para ser honesto, si no estamos hablando de millones de iteraciones, prefiero string.format para facilitar la lectura, ya que su código muestra la ventaja obvia.
mohamnag el
9

Aquí está la misma prueba que la anterior con la modificación de llamar al método toString () en StringBuilder . Los resultados a continuación muestran que el enfoque StringBuilder es solo un poco más lento que la concatenación de cadenas usando el operador + .

archivo: StringTest.java

class StringTest {

  public static void main(String[] args) {

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("Hi to you ").append(i * 2).toString();
    }

    end = System.currentTimeMillis();

    System.out.println("String Builder = " + ((end - start)) + " millisecond");

  }
}

Comandos de Shell: (compila y ejecuta StringTest 5 veces)

> javac StringTest.java
> sh -c "for i in \$(seq 1 5); do echo \"Run \${i}\"; java StringTest; done"

Resultados:

Run 1
Format = 1290 millisecond
Concatenation = 115 millisecond
String Builder = 130 millisecond

Run 2
Format = 1265 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond

Run 3
Format = 1303 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 4
Format = 1297 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 5
Format = 1270 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond
Akos Cz
fuente
6

String.format()es más que simplemente concatenar cadenas. Por ejemplo, puede mostrar números en un entorno local específico mediante String.format().

Sin embargo, si no le importa la localización, no hay diferencia funcional. Quizás el uno sea más rápido que el otro, pero en la mayoría de los casos será insignificante.

Fortega
fuente
4

En general, se debe preferir la concatenación de cadenas String.format. Este último tiene dos desventajas principales:

  1. No codifica la cadena que se construirá de manera local.
  2. El proceso de construcción está codificado en una cadena.

Por punto 1, quiero decir que no es posible entender lo que String.format()está haciendo una llamada en un solo paso secuencial. Uno se ve obligado a ir y venir entre la cadena de formato y los argumentos, mientras cuenta la posición de los argumentos. Para concatenaciones cortas, esto no es un gran problema. Sin embargo, en estos casos, la concatenación de cadenas es menos detallada.

Por punto 2, quiero decir que la parte importante del proceso de construcción está codificada en la cadena de formato (usando un DSL). El uso de cadenas para representar código tiene muchas desventajas. No es intrínsecamente seguro para escribir y complica el resaltado de sintaxis, el análisis de código, la optimización, etc.

Por supuesto, cuando se usan herramientas o marcos externos al lenguaje Java, pueden entrar en juego nuevos factores.

Stephane Bersier
fuente
2

No he hecho ningún punto de referencia específico, pero creo que la concatenación puede ser más rápida. String.format () crea un nuevo formateador que, a su vez, crea un nuevo StringBuilder (con un tamaño de solo 16 caracteres). Esa es una buena cantidad de gastos generales, especialmente si está formateando una cadena más larga y StringBuilder sigue teniendo que cambiar el tamaño.

Sin embargo, la concatenación es menos útil y más difícil de leer. Como siempre, vale la pena hacer un punto de referencia en su código para ver cuál es mejor. Las diferencias pueden ser insignificantes en la aplicación del servidor después de que sus paquetes de recursos, configuraciones regionales, etc. se carguen en la memoria y se JITTE el código.

Tal vez como mejor práctica, sería una buena idea crear su propio formateador con un StringBuilder (Appendable) y Locale de tamaño adecuado y usarlo si tiene mucho formato para hacer.

IraPayaso
fuente
2

Podría haber una diferencia perceptible.

String.format es bastante complejo y usa una expresión regular debajo, así que no acostumbres a usarlo en todas partes, sino solo donde lo necesites.

StringBuilder sería un orden de magnitud más rápido (como ya señaló alguien aquí).

Pawel Zieminski
fuente
1

Creo que podemos seguir, MessageFormat.formatya que debería ser bueno tanto en la legibilidad como en los aspectos de rendimiento.

Usé el mismo programa que usó Icaro en su respuesta anterior y lo mejoré con un código adjunto para MessageFormatexplicar los números de rendimiento.

  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = "Hi " + i + "; Hi to you " + i * 2;
    }
    long end = System.currentTimeMillis();
    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = String.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = MessageFormat.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("MessageFormat = " + ((end - start)) + " millisecond");
  }

Concatenación = 69 milisegundos

Formato = 1435 milisegundos

MessageFormat = 200 milisegundos

ACTUALIZACIONES:

Según el informe SonarLint, las cadenas de formato de estilo Printf deben usarse correctamente (calamar: S3457)

Debido a que las printf-stylecadenas de formato se interpretan en tiempo de ejecución, en lugar de ser validadas por el compilador, pueden contener errores que provocan la creación de cadenas incorrectas. Esta regla valida estáticamente la correlación de printf-stylelas cadenas de formato a sus argumentos al llamar a los métodos de formato (...) de java.util.Formatter, java.lang.String, java.io.PrintStream, MessageFormat, y java.io.PrintWriterlas clases y los printf(...)métodos de la java.io.PrintStreamo java.io.PrintWriterlas clases.

Reemplazo el estilo printf con las llaves y obtuve resultados interesantes como a continuación.

Concatenación = 69 milisegundos
Formato = 1107 milisegundos
Formato: curly-brackets = 416 milisegundos
MessageFormat = 215 milisegundos
MessageFormat: curly-brackets = 2517 milisegundos

Mi conclusión:
como destaqué anteriormente, usar String.format con llaves debe ser una buena opción para obtener beneficios de buena legibilidad y también rendimiento.

Ganesa Vijayakumar
fuente
0

No puede comparar String Concatenation y String.Format por el programa anterior.

Puede intentar esto también intercambiando la posición de usar su String.Format and Concatenation en su bloque de código como el siguiente

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = String.format( "Hi %s; Hi to you %s",i, + i*2);
  }

  long end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
  start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }

  end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;
}

Te sorprenderá ver que el formato funciona más rápido aquí. Esto se debe a que los objetos iniciales creados pueden no liberarse y puede haber un problema con la asignación de memoria y, por lo tanto, con el rendimiento.

DotNetUser
fuente
3
¿Has probado tu código? La concatenación siempre es diez veces más rápida
Icaro
¿Qué pasa con los millis tomados para ejecutar este "System.currentTimeMillis ()": P.
rehan
0

Toma un poco de tiempo acostumbrarse a String.Format, pero vale la pena en la mayoría de los casos. En el mundo de NRA (nunca repita nada) es extremadamente útil mantener sus mensajes tokenizados (registro o usuario) en una biblioteca Constant (prefiero lo que equivale a una clase estática) y llamarlos según sea necesario con String.Format independientemente de si están localizando o no. Intentar utilizar una biblioteca de este tipo con un método de concatenación es más difícil de leer, solucionar problemas, corregir y administrar con cualquier enfoque que requiera concatenación. El reemplazo es una opción, pero dudo que sea eficiente. Después de años de uso, mi mayor problema con String.Format es que la duración de la llamada es inconvenientemente larga cuando la paso a otra función (como Msg), pero eso es fácil de solucionar con una función personalizada para servir como un alias .

usuario665056
fuente