Domanda

Dobbiamo costruire le stringhe tutto il tempo per l'uscita di registro e così via. Nel corso degli versioni JDK abbiamo imparato quando utilizzare StringBuffer (molti accodamento, thread-safe) e StringBuilder (molti accodamento, non thread-safe).

Qual è il parere sull'uso String.format()? E 'efficiente, o siamo costretti ad attaccare con la concatenazione di battute in cui le prestazioni sono importanti?

es. brutto vecchio stile,

String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";

vs. ordine nuovo stile (String.Format, che è forse più lento),

String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);

Nota: il mio caso specifico uso è le centinaia di stringhe di registro 'uno-liner' in tutto il mio codice. Essi non comportano un ciclo, in modo da <=> è troppo pesante. Mi interessa <=> specifico.

È stato utile?

Soluzione

Ho scritto una piccola classe per testare che ha la migliore performance dei due e + arriva prima del formato. di un fattore 5 a 6. Provatelo voi stessi

import java.io.*;
import java.util.Date;

public class StringTest{

    public static void main( String[] args ){
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;

    for( i = 0; i< 100000; i++){
        String s = "Blah" + i + "Blah";
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<100000; i++){
        String s = String.format("Blah %d Blah", i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    }
}

Esecuzione del sopra per diverso N mostra che entrambi si comportano in modo lineare, ma String.format è 5-30 volte più lento.

La ragione è che nell'implementazione corrente StringBuilder.append primo analizza l'ingresso con le espressioni regolari e quindi riempie i parametri. Concatenazione con plus, invece, viene ottimizzata dal javac (non dalla JIT) e usi <=> direttamente.

confronto Runtime

Altri suggerimenti

hhafez codice e aggiunto un test di memoria :

private static void test() {
    Runtime runtime = Runtime.getRuntime();
    long memory;
    ...
    memory = runtime.freeMemory();
    // for loop code
    memory = memory-runtime.freeMemory();

ho eseguito questo separatamente per ogni approccio, l'operatore '+', String.Format e StringBuilder (chiamando toString ()), quindi la memoria utilizzata non sarà influenzato dagli altri approcci. Ho aggiunto più concatenazioni, rendendo la stringa come "Blah" + i + "Blah" + i + "Blah" + i + "Blah".

Il risultato sono i seguenti (media di 5 piste ciascuno):
Approach Time (ms) di memoria allocata (lungo)
'+' Operatore 747 320.504
String.Format 16484 373.312
StringBuilder 769 57.344

Possiamo vedere che String '+' e StringBuilder sono praticamente identici tempo-saggio, ma StringBuilder è molto più efficiente in uso della memoria. Questo è molto importante quando abbiamo molte chiamate di registro (o qualsiasi altra indicazione che coinvolgono le stringhe) in un intervallo di tempo abbastanza breve in modo che il Garbage Collector non si arriva a pulire i molti casi stringa risultante dell'operatore '+'.

E una nota, BTW, non dimenticate di controllare la registrazione Livello prima di costruire il messaggio.

Conclusioni:

  1. Io continuo a utilizzare StringBuilder.
  2. Ho troppo tempo o troppo poco la vita.

Tutti i parametri di riferimento qui presentati hanno alcuni difetti , quindi i risultati non sono affidabili.

Sono rimasto sorpreso che nessuno utilizzato JMH per il benchmarking, così ho fatto .

Risultati:

Benchmark             Mode  Cnt     Score     Error  Units
MyBenchmark.testOld  thrpt   20  9645.834 ± 238.165  ops/s  // using +
MyBenchmark.testNew  thrpt   20   429.898 ±  10.551  ops/s  // using String.format

Le unità sono di operazioni al secondo, più sono e meglio. Benchmark codice sorgente . OpenJDK IcedTea 2.5.4 Java Virtual Machine è stato utilizzato.

Quindi, vecchio stile (con +) è molto più veloce.

Il vecchio e brutto stile viene compilato automaticamente dal JAVAC 1.6 come:

StringBuilder sb = new StringBuilder("What do you get if you multiply ");
sb.append(varSix);
sb.append(" by ");
sb.append(varNine);
sb.append("?");
String s =  sb.toString();

Quindi non c'è assolutamente nessuna differenza tra questo e utilizzando uno StringBuilder.

String.Format è molto più pesante in quanto crea una nuova Formatter, analizza la stringa di formato di input, crea uno StringBuilder, aggiungere tutto ciò ad esso e chiama toString ().

String.Format

Java funziona in questo modo:

  1. analizza la stringa di formato, esplodendo in una lista di pezzi di formato
  2. esso itera i pezzi formati, rendendo in uno StringBuilder, che è fondamentalmente una matrice che si ridimensiona se necessario, copiando in una nuova matrice. ciò è necessario perché non sappiamo ancora come grande per allocare la stringa finale
  3. StringBuilder.toString) copie (il suo buffer interno in una nuova stringa

se la destinazione finale per questi dati è un flusso (ad esempio il rendering di una pagina web o scrittura in un file), è possibile assemblare i pezzi di formato direttamente nel tuo stream:

new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");

I speculare che l'ottimizzatore ottimizzerà via l'elaborazione stringa di formato. Se è così, si è lasciato con equivalente rel="noreferrer"> per srotolare manualmente lo String.Format in uno StringBuilder .

Per espandere / corretta sulla prima risposta di cui sopra, non è che la traduzione String.Format avrebbe aiutato con, in realtà.
Che String.Format aiuterà con è quando si stampa una data / ora (o un formato numerico, ecc), dove ci sono la localizzazione (L10N) differenze (ad esempio, alcuni paesi verranno stampati 04Feb2009 e altri stamperanno Feb042009).
Con la traduzione, si sta parlando solo di spostare tutte le stringhe Externalizable (come i messaggi di errore e che cosa-no) in un fascio proprietà in modo che è possibile utilizzare il pacchetto giusto per la lingua giusta, utilizzando ResourceBundle e MessageFormat.

Guardando tutto quanto sopra, direi che le prestazioni-saggio, String.Format vs pianura concatenazione si riduce a ciò che si preferisce. Se si preferisce guardare le chiamate al .format sopra concatenamento, quindi con tutti i mezzi, andare con quella.
Dopo tutto, il codice viene letto molto più di quello che è scritto.

Nel tuo esempio, le prestazioni probalby non è troppo diverso, ma ci sono altre questioni da considerare: e cioè la frammentazione di memoria. Anche il funzionamento concatenare sta creando una nuova stringa, anche se la sua temporanea (ci vuole tempo per GC è ed è più di lavoro). String.Format () è solo più leggibile e si tratta di una minore frammentazione.

Inoltre, se si sta utilizzando un particolare formato un sacco, non dimenticare è possibile utilizzare la classe Formatter () direttamente (tutti String.Format () non fa altro che istanziare un'istanza Formatter un uso).

Inoltre, un'altra cosa che si dovrebbe essere a conoscenza di: fare attenzione di utilizzare substring (). Ad esempio:

String getSmallString() {
  String largeString = // load from file; say 2M in size
  return largeString.substring(100, 300);
}

Questa stringa di grandi dimensioni è ancora nella memoria, perché questo è solo come lavoro Java stringhe. Una versione migliore è:

  return new String(largeString.substring(100, 300));

o

  return String.format("%s", largeString.substring(100, 300));

La seconda forma è probabilmente più utile se si sta facendo altre cose allo stesso tempo.

In genere si dovrebbe usare String.Format perché è relativamente veloce e supporta la globalizzazione (supponendo che si sta effettivamente cercando di scrivere qualcosa che viene letto da parte dell'utente). E rende anche più facile per globalizzare se si sta cercando di tradurre una stringa rispetto a 3 o più secondo il prospetto (in particolare per le lingue che hanno drasticamente diverse strutture grammaticali).

Ora, se non hai mai intenzione di tradurre qualsiasi cosa, allora o si basano su Java incorporato nella conversione del + operatori in StringBuilder. Oppure utilizzare Java <=> esplicitamente.

Un'altra prospettiva dal punto di vista Logging Solo.

Vedo un sacco di discussioni relative alla registrazione su questo thread così pensato di aggiungere la mia esperienza in risposta. Può essere che qualcuno lo troverà utile.

Credo che la motivazione di memorizzazione con formattatore viene da evitare la concatenazione di stringhe. In sostanza, non si vuole avere un overhead di concat stringa se non si ha intenzione di accedere esso.

Non hai veramente bisogno di concat / formato a meno che non si desidera accedere. Consente di dire se mi definisco un metodo come questo

public void logDebug(String... args, Throwable t) {
    if(debugOn) {
       // call concat methods for all args
       //log the final debug message
    }
}

In questo approccio l'cancat / formattatore non è davvero chiamato affatto se un messaggio di debug e debugOn = false

Anche se sarà ancora meglio usare StringBuilder invece di formattatore qui. La motivazione principale è quello di evitare nulla di tutto ciò.

Allo stesso tempo, non mi piace l'aggiunta di "se" blocco per ogni dichiarazione di registrazione in quanto

  • Colpisce leggibilità
  • Riduce la copertura sul mio test di unità - questo è fonte di confusione quando si vuole assicurarsi che ogni linea è testato.

Quindi preferisco per creare una classe di utilità di registrazione con metodi come sopra e usarlo ovunque senza preoccuparsi di calo di prestazioni e di tutte le altre questioni ad essa collegate.

Ho appena modificato il test di hhafez per includere StringBuilder. StringBuilder è di 33 volte più veloce rispetto String.Format usando JDK 1.6.0_10 client su XP. Usando l'interruttore -server abbassa il fattore di 20.

public class StringTest {

   public static void main( String[] args ) {
      test();
      test();
   }

   private static void test() {
      int i = 0;
      long prev_time = System.currentTimeMillis();
      long time;

      for ( i = 0; i < 1000000; i++ ) {
         String s = "Blah" + i + "Blah";
      }
      time = System.currentTimeMillis() - prev_time;

      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         String s = String.format("Blah %d Blah", i);
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);

      prev_time = System.currentTimeMillis();
      for ( i = 0; i < 1000000; i++ ) {
         new StringBuilder("Blah").append(i).append("Blah");
      }
      time = System.currentTimeMillis() - prev_time;
      System.out.println("Time after for loop " + time);
   }
}

Anche se questo potrebbe sembrare drastica, ritengo che sia rilevante solo in rari casi, perché i numeri assoluti sono piuttosto bassi: 4 s per 1 milione di chiamate String.Format semplici è una sorta di ok - fino a quando li uso per registrazione o simili.

Aggiornamento: Come sottolineato da sjbotha nei commenti, il test StringBuilder non è valido, in quanto manca un .toString() finale.

Il fattore di accelerazione corretta dal String.format(.) a StringBuilder è 23 sulla mia macchina (16 con la -server interruttore).

Qui si modifica la versione di entrata hhafez. Esso include un'opzione di costruttore di stringa.

public class BLA
{
public static final String BLAH = "Blah ";
public static final String BLAH2 = " Blah";
public static final String BLAH3 = "Blah %d Blah";


public static void main(String[] args) {
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;
    int numLoops = 1000000;

    for( i = 0; i< numLoops; i++){
        String s = BLAH + i + BLAH2;
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        String s = String.format(BLAH3, i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<numLoops; i++){
        StringBuilder sb = new StringBuilder();
        sb.append(BLAH);
        sb.append(i);
        sb.append(BLAH2);
        String s = sb.toString();
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

}

}

Ora dopo ciclo for 391 Di volta in ciclo for 4163 Di volta in ciclo for 227

La risposta a questa dipende molto dal modo in cui il compilatore Java specifica ottimizza il bytecode che genera. Le stringhe sono immutabili e, teoricamente, ogni operazione "+" in grado di creare uno nuovo. Ma, il compilatore ottimizza quasi certamente lontano passaggi intermedi nella costruzione di lunghe stringhe. E 'del tutto possibile che entrambe le linee di codice di cui sopra generano la stessa identica bytecode.

L'unico vero modo per conoscere è quello di testare il codice in modo iterativo nel vostro ambiente attuale. Scrivi un'applicazione QD che concatena le stringhe in entrambe le direzioni in modo iterativo e vedere come si volta contro l'altro.

Si consiglia di utilizzare "hello".concat( "world!" ) per piccolo numero di stringhe in concatenazione. Potrebbe essere ancora migliore per le prestazioni rispetto ad altri approcci.

Se si dispone di più di 3 stringhe, di considerare l'utilizzo di StringBuilder, o semplicemente stringa, a seconda del compilatore che si utilizza.

scroll top