Concatenazione di stringhe: concat () vs operatore “+”

Supponendo String aeb:

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

Sotto il cofano, sono la stessa cosa?

Ecco concat decompilato come riferimento. Mi piacerebbe essere in grado di decompilare anche l’operatore + per vedere cosa fa.

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

No, non proprio.

Innanzitutto, c’è una leggera differenza nella semantica. Se a è null , allora a.concat(b) genera una NullPointerException ma a+=b tratterà il valore originale di a come se fosse null . Inoltre, il metodo concat() accetta solo valori di String mentre l’operatore + converte in silenzio l’argomento in una stringa (usando il metodo toString() per gli oggetti). Quindi il metodo concat() è più rigido in ciò che accetta.

Per guardare sotto il cofano, scrivi una class semplice con a += b;

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

Ora smonta con javap -c (incluso in Sun JDK). Dovresti vedere una lista che include:

 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."":()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 

Quindi, a += b è l’equivalente di

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

Il metodo concat dovrebbe essere più veloce. Tuttavia, con più stringhe vince il metodo StringBuilder , almeno in termini di prestazioni.

Il codice sorgente di String e StringBuilder (e la sua class base privata pacchetto) è disponibile in src.zip di Sun JDK. Puoi vedere che stai costruendo un array di caratteri (ridimensionando se necessario) e poi buttandolo via quando crei la String finale. In pratica l’allocazione della memoria è sorprendentemente veloce.

Aggiornamento: come osserva Pawel Adamski, le prestazioni sono cambiate in HotSpot più recente. javac produce ancora esattamente lo stesso codice, ma il codice del bytecode imbroglia. Il semplice test fallisce completamente perché l’intero codice del codice viene gettato via. Sommando System.identityHashCode (not String.hashCode ) mostra che il codice StringBuffer ha un leggero vantaggio. Sobject a modifiche quando viene rilasciato il prossimo aggiornamento o se si utilizza una JVM diversa. Da @lukaseder , un elenco di elementi intrinseci di HotSpot JVM .

Niyaz è corretto, ma vale anche la pena notare che l’operatore speciale + può essere convertito in qualcosa di più efficiente dal compilatore Java. Java ha una class StringBuilder che rappresenta una stringa mutabile non thread-safe. Quando si esegue un gruppo di concatenazioni di stringhe, il compilatore Java viene convertito automaticamente

 String a = b + c + d; 

in

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

che per le grandi stringhe è significativamente più efficiente. Per quanto ne so, questo non succede quando usi il metodo concat.

Tuttavia, il metodo concat è più efficiente quando si concatena una stringa vuota su una stringa esistente. In questo caso, la JVM non ha bisogno di creare un nuovo object String e può semplicemente restituire quello esistente. Vedere la documentazione di concat per confermare questo.

Quindi, se sei estremamente preoccupato per l’efficienza, dovresti usare il metodo concatenando concatenazioni di stringhe eventualmente vuote e usare + altrimenti. Tuttavia, la differenza di prestazioni dovrebbe essere trascurabile e probabilmente non dovresti mai preoccuparti di questo.

Ho eseguito un test simile a @marcio ma con il seguente ciclo invece:

 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 } 

Solo per buona misura, ho lanciato anche StringBuilder.append() . Ogni test è stato eseguito 10 volte, con 100k ripetizioni per ogni corsa. Ecco i risultati:

  • StringBuilder vince a mani basse. Il risultato del tempo di clock era 0 per la maggior parte delle esecuzioni e il più lungo ha richiesto 16ms.
  • a += b richiede circa 40000ms (40 secondi) per ogni corsa.
  • concat richiede solo 10000 ms (10 s) per corsa.

Non ho decompilato la class per vedere gli interni o ancora eseguirla attraverso il profiler, ma sospetto che a += b passi la maggior parte del tempo a creare nuovi oggetti di StringBuilder e poi a riconvertirli in String .

Tom ha ragione nel descrivere esattamente cosa fa l’operatore +. Crea uno StringBuilder temporaneo, aggiunge le parti e termina con toString() .

Tuttavia, tutte le risposte finora ignorano gli effetti delle ottimizzazioni del runtime di HotSpot. Nello specifico, queste operazioni temporanee sono riconosciute come un modello comune e vengono sostituite con un codice macchina più efficiente in fase di esecuzione.

@marcio: hai creato un micro-benchmark ; con la JVM moderna questo non è un modo valido per codificare il codice.

La ragione per cui l’ottimizzazione del tempo di esecuzione è importante è che molte di queste differenze nel codice, compresa la creazione di oggetti, sono completamente diverse una volta che HotSpot è in esecuzione. L’unico modo per saperlo è profilare il codice in situ .

Infine, tutti questi metodi sono infatti incredibilmente veloci. Questo potrebbe essere un caso di ottimizzazione prematura. Se hai un codice che concatena molto le stringhe, il modo per ottenere la massima velocità probabilmente non ha nulla a che fare con gli operatori che scegli e invece con l’algoritmo che stai usando!

Che ne dici di alcuni semplici test? Usato il codice qui sotto:

 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); 
  • La versione "a + b" eseguita in 2500 ms .
  • a.concat(b) eseguito in 1200ms .

Testato più volte. L'esecuzione della versione concat() richiedeva in media la metà del tempo.

Questo risultato mi ha sorpreso perché il metodo concat() crea sempre una nuova stringa (restituisce una " new String(result) ". È noto che:

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

Perché il compilatore non era in grado di ottimizzare la creazione di stringhe nel codice "a + b", sapendo che il risultato era sempre la stessa stringa? Potrebbe evitare una nuova creazione di stringhe. Se non credi alla dichiarazione di cui sopra, prova per te stesso.

La maggior parte delle risposte qui sono del 2008. Sembra che le cose siano cambiate nel tempo. I miei ultimi benchmark realizzati con JMH mostrano che su Java 8 + è circa due volte più veloce di concat .

Il mio punto di riferimento:

 @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 = "[email protected]#"; } @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)))); } } 

risultati:

 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 

Fondamentalmente, ci sono due importanti differenze tra + e il metodo concat .

  1. Se si utilizza il metodo concat , si sarà in grado di concatenare stringhe solo se nel caso dell’operatore + è ansible concatenare la stringa con qualsiasi tipo di dati.

    Per esempio:

     String s = 10 + "Hello"; 

    In questo caso, l’output dovrebbe essere 10Hello .

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

    Nel caso precedente devi fornire due stringhe obbligatorie.

  2. La seconda e principale differenza tra + e concat è che:

    Caso 1: Supponiamo di concatenare le stesse stringhe con l’operatore concat in questo modo

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

    In questo caso il numero totale di oggetti creati nel pool è 7 come questo:

     I am good boy Iam Iamgood Iamgoodboy 

    Caso 2:

    Ora ho intenzione di concatinare le stesse stringhe tramite + operatore

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

    Nel caso precedente, il numero totale di oggetti creati è solo 5.

    In realtà quando concateniamo le stringhe tramite l’operatore + , mantiene una class StringBuffer per eseguire la stessa attività come segue:

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

    In questo modo creerà solo cinque oggetti.

Quindi ragazzi queste sono le differenze di base tra + e il metodo concat . Godere 🙂

Per completezza, volevo aggiungere che la definizione dell’operatore ‘+’ può essere trovata nel JLS SE8 15.18.1 :

Se solo un’espressione di operando è di tipo String, la conversione di stringhe (§5.1.11) viene eseguita sull’altro operando per produrre una stringa in fase di esecuzione.

Il risultato della concatenazione di stringhe è un riferimento a un object String che è la concatenazione delle due stringhe di operando. I caratteri dell’operando di sinistra precedono i caratteri dell’operando di destra nella stringa appena creata.

L’object String è stato appena creato (§12.5) a meno che l’espressione sia un’espressione costante (§15.28).

Riguardo all’implementazione, JLS afferma quanto segue:

Un’implementazione può scegliere di eseguire la conversione e la concatenazione in un solo passaggio per evitare di creare e quindi scartare un object String intermedio. Per aumentare le prestazioni della concatenazione di stringhe ripetute, un compilatore Java può utilizzare la class StringBuffer o una tecnica simile per ridurre il numero di oggetti String intermedi creati dalla valutazione di un’espressione.

Per i tipi primitivi, un’implementazione può anche ottimizzare la creazione di un object wrapper convertendo direttamente da un tipo primitivo a una stringa.

Quindi, a giudicare dal ‘un compilatore Java può usare la class StringBuffer o una tecnica simile per ridurre’, diversi compilatori potrebbero produrre codice byte differente.

L’ operatore + può lavorare tra una stringa e un valore di tipo di dati stringa, char, integer, double o float. Converte semplicemente il valore nella sua rappresentazione di stringa prima della concatenazione.

L’ operatore concat può essere eseguito solo con e con le stringhe. Verifica la compatibilità del tipo di dati e genera un errore, se non corrispondono.

Tranne questo, il codice che hai fornito fa la stessa roba.

Io non la penso così

a.concat(b) è implementato in String e penso che l’implementazione non sia cambiata molto dalle prime macchine java. L’implementazione dell’operazione + dipende dalla versione e dal compilatore di Java. Attualmente + è implementato usando StringBuffer per rendere l’operazione il più veloce ansible. Forse in futuro, questo cambierà. Nelle precedenti versioni di java + operazione su Stringhe era molto più lenta in quanto produceva risultati intermedi.

Immagino che += sia implementato usando + e similmente ottimizzato.

Quando si usa +, la velocità diminuisce all’aumentare della lunghezza della corda, ma quando si usa concat, la velocità è più stabile, e l’opzione migliore è usare la class StringBuilder che ha una velocità stabile per farlo.

Immagino tu possa capire perché. Ma il modo migliore per creare stringhe lunghe è l’uso di StringBuilder () e append (), o la velocità sarà inaccettabile.