È meglio usare String.format su Concatenazione di stringhe in Java?


273

C'è una differenza percettibile tra l'utilizzo String.formate la concatenazione di stringhe in Java?

Tendo a usare String.formatma ogni tanto scivolerò e userò una concatenazione. Mi chiedevo se uno fosse migliore dell'altro.

Per come la vedo io, String.formatti dà più potenza nel "formattare" la stringa; e la concatenazione significa che non devi preoccuparti di aggiungere accidentalmente un% s in più o di perdere uno.

String.format è anche più breve.

Quale è più leggibile dipende da come funziona la tua testa.


Penso che possiamo andare con MessageFormat.format. Per ulteriori informazioni, consultare la risposta stackoverflow.com/a/56377112/1491414 .
Ganesa Vijayakumar,

Risposte:


242

Suggerirei che è una pratica migliore da usare String.format(). Il motivo principale è che String.format()può essere localizzato più facilmente con il testo caricato da file di risorse mentre la concatenazione non può essere localizzata senza produrre un nuovo eseguibile con codice diverso per ogni lingua.

Se prevedi che la tua app sia localizzabile, dovresti anche prendere l'abitudine di specificare le posizioni degli argomenti anche per i token di formato:

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

Questo può quindi essere localizzato e scambiare i token di nome e ora senza richiedere una ricompilazione dell'eseguibile per tenere conto del diverso ordinamento. Con le posizioni degli argomenti puoi anche riutilizzare lo stesso argomento senza passarlo due volte nella funzione:

String.format("Hello %1$s, your name is %1$s and the time is %2$t", name, time)

1
Puoi indicarmi un po 'di documentazione che parla di come lavorare con le posizioni / l'ordine degli argomenti in Java (cioè, come fare riferimento agli argomenti in base alla loro posizione)? Grazie.
markvgti

13
Meglio tardi che mai, versione casuale di Java: docs.oracle.com/javase/1.5.0/docs/api/java/util/…
Aksel

174

A proposito di prestazioni:

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

I risultati dei tempi sono i seguenti:

  • Concatenazione = 265 millisecondi
  • Formato = 4141 millisecondi

Pertanto, la concatenazione è molto più veloce di String.format.


15
Sono tutte cattive pratiche. Usa StringBuilder.
Amir Raminfar,

8
StringBuilder qui non rientra nell'ambito di applicazione (la domanda dell'OP riguardava il confronto di String.format con la concatenazione di stringhe) ma hai dati performace su String Builder?
Icaro

108
@AmirRaminar: il compilatore converte automaticamente "+" in chiamate a StringBuilder.
Martin Schröder,

40
@ MartinSchröder: Se esegui javap -c StringTest.classvedrai che il compilatore converte automaticamente "+" in StringBuilder solo se non sei in un ciclo. Se la concatenazione viene eseguita su una sola riga è come usare '+', ma se usi myString += "morechars";o myString += anotherString;su più righe noterai che potrebbe essere creato più di un StringBuilder, quindi l'uso di "+" non è sempre così efficiente come StringBuilder.
ccpizza,

7
@Joffrey: quello che intendevo dire era che per i loop +non ci si converte StringBuilder.append()ma invece new StringBuilder()accade a ogni iterazione.
ccpizza,

39

Dato che c'è una discussione sulle prestazioni, ho pensato di aggiungere un confronto che includesse StringBuilder. È infatti più veloce dell'opzione concat e, naturalmente, dell'opzione String.format.

Per renderlo una sorta di confronto tra mele e mele ho istanziato un nuovo StringBuilder nel loop piuttosto che all'esterno (questo è in realtà più veloce di fare solo un'istanza molto probabilmente a causa del sovraccarico di riassegnare spazio per l'appendice di loop alla fine di un costruttore).

    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");
  • 11/01/2012 16: 30: 46.058 INFO [TestMain] - Formato = 1416 millisecondi
  • 11/01/2012 16: 30: 46.190 INFO [TestMain] - Concatenazione = 134 millisecondi
  • 11/01/2012 16: 30: 46.313 INFO [TestMain] - String Builder = 117 millisecondi

21
Il test StringBuilder non chiama toString (), quindi non è un confronto equo. Sospetto che troverai l'errore di misurazione delle prestazioni della concatenazione se correggi quel bug.
Jamey Sharp,

15
Nei test di concatenazione e formato, hai chiesto a String. Il test StringBuilder, per essere onesti, ha bisogno di un passaggio finale che trasforma il contenuto di StringBuilder in una stringa. Lo fai chiamando bldString.toString(). Spero che questo lo spieghi?
Jamey Sharp,

4
Jamey Sharp ha esattamente ragione. Invocare bldString.toString () è più o meno lo stesso se non più lento della concatenazione di stringhe.
Akos Cz,

3
String s = bldString.toString(); I tempi erano con la concatenazione e StringBuilder quasi alla pari con l'altro: Format = 1520 millisecond, Concatenation = 167 millisecond, String Builder = 173 millisecond mi sono imbattuto in un ciclo e una media di ciascun fuori per ottenere un buon rappresentante: (ottimizzazione pre-JVM, cercherà un ciclo 10000+ quando avrò tempo)
TechTrip

3
Come fate a sapere se il codice viene eseguito? Le variabili non vengono mai lette o utilizzate, non puoi essere sicuro che JIT non rimuova questo codice in primo luogo.
alobodzk,

37

Un problema .formatè che si perde la sicurezza di tipo statico. Puoi avere troppi argomenti per il tuo formato e puoi avere tipi sbagliati per gli identificatori di formato - entrambi portano a un IllegalFormatException runtime , quindi potresti finire con un codice di registrazione che interrompe la produzione.

Al contrario, gli argomenti +possono essere testati dal compilatore.

La storia della sicurezza di(su cui formatè modellata la funzione) è lungo e spaventoso.


16
solo per la cronaca, gli IDE moderni (ad esempio IntelliJ) assistono nel conteggio degli argomenti e nella corrispondenza dei tipi
Ron Klein,

2
Un buon punto per la compilazione, ti consiglio di fare questi controlli tramite FindBugs (che può essere eseguito nell'IDE o tramite Maven durante la compilazione), nota che controllerà anche la formattazione in tutte le tue registrazioni! Funziona indipendentemente dall'IDE dell'utente
Christophe Roussy,

20

Quale è più leggibile dipende da come funziona la tua testa.

Hai la tua risposta proprio lì.

È una questione di gusti personali.

La concatenazione di stringhe è leggermente più veloce, suppongo, ma dovrebbe essere trascurabile.


3
Sono d'accordo. Pensare alle differenze di prestazioni qui è principalmente solo un'ottimizzazione prematura - nell'improbabile caso in cui la profilazione mostri che c'è un problema qui, quindi preoccupati.
Jonik,

3
È solo una questione di gusti personali se il progetto è piccolo e non intende mai essere internazionalizzato in alcun senso significativo. Altrimenti String.format vince sulla concatenazione in ogni modo.
workmad3,

4
Non sono d'accordo. Non importa quanto sia grande il progetto, difficilmente riuscirai a localizzare ogni stringa che sia mai stata costruita al suo interno. In altre parole, dipende dalla situazione (a cosa servono le stringhe).
Jonik,

Non riesco a immaginare come qualcuno considererebbe mai 'String.format ("% s% s", a, b)' più leggibile di 'a + b', e data la differenza dell'ordine di grandezza nella velocità che la risposta mi sembra chiara (in situazioni che non richiedono localizzazione come debug o la maggior parte delle dichiarazioni di registrazione).
BobDoolittle,

16

Ecco un test con più dimensioni del campione in millisecondi.

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

1
StringBuilder è assolutamente il metodo più veloce quando si aggiungono caratteri in un ciclo, ad esempio, quando si desidera creare una stringa con mille 1 aggiungendoli uno per uno. Ecco maggiori informazioni: pellegrino.link/2015/08/22/…
Carlos Hoyos,

Mi piace come usi sempre String.format per l'output: D, quindi c'è un vantaggio. e ad essere onesti se non stiamo parlando di milioni di iterazioni, preferisco string.format per la leggibilità poiché il tuo codice mostra l'ovvio vantaggio!
Mohamnag,

9

Ecco lo stesso test di cui sopra con la modifica della chiamata del metodo toString () su StringBuilder . I risultati seguenti mostrano che l'approccio StringBuilder è solo un po 'più lento della concatenazione di stringhe usando l' operatore + .

file: 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");

  }
}

Comandi Shell: (compilare ed eseguire StringTest 5 volte)

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

Risultati:

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

6

String.format()è molto più che concatenare stringhe. Ad esempio, è possibile visualizzare i numeri in una locale specifica utilizzando String.format().

Tuttavia, se non ti interessa la localizzazione, non ci sono differenze funzionali. Forse l'uno è più veloce dell'altro, ma nella maggior parte dei casi sarà trascurabile ..


4

In generale, la concatenazione di stringhe dovrebbe essere preferita rispetto a String.format. Quest'ultimo ha due principali svantaggi:

  1. Non codifica la stringa da costruire in modo locale.
  2. Il processo di costruzione è codificato in una stringa.

Per punto 1, intendo che non è possibile capire cosa String.format()sta facendo una chiamata in un singolo passaggio sequenziale. Uno è costretto ad andare avanti e indietro tra la stringa di formato e gli argomenti, contando la posizione degli argomenti. Per concatenazioni brevi, questo non è un grosso problema. In questi casi, tuttavia, la concatenazione di stringhe è meno dettagliata.

Per punto 2, intendo che la parte importante del processo di costruzione è codificata nella stringa di formato (usando un DSL). L'uso delle stringhe per rappresentare il codice presenta molti svantaggi. Non è intrinsecamente sicuro per i tipi e complica l'evidenziazione della sintassi, l'analisi del codice, l'ottimizzazione, ecc.

Naturalmente, quando si utilizzano strumenti o framework esterni al linguaggio Java, possono entrare in gioco nuovi fattori.


2

Non ho fatto benchmark specifici, ma penso che la concatenazione potrebbe essere più veloce. String.format () crea un nuovo Formatter che, a sua volta, crea un nuovo StringBuilder (con una dimensione di soli 16 caratteri). Questa è una buona dose di overhead, specialmente se stai formattando una stringa più lunga e StringBuilder continua a dover ridimensionare.

Tuttavia, la concatenazione è meno utile e più difficile da leggere. Come sempre, vale la pena fare un benchmark sul tuo codice per vedere quale è meglio. Le differenze potrebbero essere trascurabili nell'app del server dopo che i pacchetti di risorse, le impostazioni locali, ecc. Sono stati caricati in memoria e il codice è JITted.

Forse come best practice, sarebbe una buona idea creare il proprio Formatter con StringBuilder (Appendable) e impostazioni locali di dimensioni adeguate e utilizzarlo se si ha molta formattazione da fare.


2

Potrebbe esserci una differenza percettibile.

String.format è piuttosto complesso e utilizza un'espressione regolare sotto, quindi non prendere l'abitudine di usarlo ovunque, ma solo dove ne hai bisogno.

StringBuilder sarebbe un ordine di grandezza più veloce (come qualcuno ha già sottolineato).


1

Penso che possiamo andare avanti MessageFormat.formatperché dovrebbe essere buono sia in termini di leggibilità sia in termini di prestazioni.

Ho usato lo stesso programma utilizzato da Icaro nella sua risposta sopra e l'ho migliorato con l'aggiunta di codice per l'utilizzo MessageFormatper spiegare i numeri delle prestazioni.

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

Concatenazione = 69 millisecondi

Formato = 1435 millisecondi

MessageFormat = 200 millisecondi

AGGIORNAMENTI:

Come da SonarLint Report, le stringhe di formato in stile Printf devono essere utilizzate correttamente (calamari: S3457)

Poiché le printf-stylestringhe di formato vengono interpretate in fase di esecuzione, anziché convalidate dal compilatore, possono contenere errori che provocano la creazione di stringhe errate. Questa regola convalida staticamente la correlazione di printf-stylestringhe di formato per i loro argomenti quando si chiama i metodi format (...) di java.util.Formatter, java.lang.String, java.io.PrintStream, MessageFormat, e java.io.PrintWriterle classi ei printf(...)metodi di java.io.PrintStreamo java.io.PrintWriterclassi.

Sostituisco lo stile printf con le parentesi graffe e ho ottenuto risultati interessanti come di seguito.

Concatenazione = 69 millisecondi
Formato = 1107 millisecondi
Formato: parentesi graffe = 416 millisecondi
MessageFormat = 215 millisecondi
MessageFormat: parentesi graffe = 2517 millisecondi

La mia conclusione:
Come ho sottolineato sopra, l'uso di String.format con parentesi graffe dovrebbe essere una buona scelta per ottenere benefici di buona leggibilità e anche prestazioni.


0

Non puoi confrontare String Concatenation e String.Format con il programma sopra.

Puoi provare questo anche scambiando la posizione dell'uso di String.Format e Concatenation nel tuo blocco di codice come il seguente

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

Sarai sorpreso di vedere che Format funziona più velocemente qui. Questo perché gli oggetti iniziali creati potrebbero non essere rilasciati e potrebbe esserci un problema con l'allocazione della memoria e quindi le prestazioni.


3
hai provato il tuo codice? La concatenazione è sempre dieci volte più veloce
Icaro

che dire dei millis presi per eseguire questo "System.currentTimeMillis ()": P.
Rehan,

0

Ci vuole un po 'di tempo per abituarsi a String.Format, ma nella maggior parte dei casi ne vale la pena. Nel mondo di NRA (non ripetere mai nulla) è estremamente utile conservare i messaggi token (registrazione o utente) in una libreria Constant (preferisco ciò che equivale a una classe statica) e chiamarli come necessari con String.Format indipendentemente dal fatto che tu stanno localizzando o no. Cercare di utilizzare una libreria di questo tipo con un metodo di concatenazione è più difficile da leggere, risolvere i problemi, correggere e gestire con qualsiasi approccio che richiede la concatenazione. La sostituzione è un'opzione, ma dubito che sia performante. Dopo anni di utilizzo, il mio più grande problema con String.Format è che la durata della chiamata è scomodamente lunga quando la sto passando in un'altra funzione (come Msg), ma è facile spostarsi con una funzione personalizzata per fungere da alias .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.