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


499

Supponendo la stringa aeb:

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

Sotto il cofano, sono la stessa cosa?

Qui è concat decompilato come riferimento. Mi piacerebbe poter 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);
    }
}

3
possibile duplicato di concatenazione
Greg Mattes,

3
Non sono sicuro che +possa essere decompilato.
Galen Nare,

1
Utilizzare javap per disassemblare un file di classe Java.
Hot Licks,

A causa dell '"immutabilità" dovresti probabilmente usare StringBuffero StringBuilder- (thread non sicuro quindi più veloce, invece
Ujjwal Singh,

Risposte:


560

No, non del tutto.

Innanzitutto, c'è una leggera differenza nella semantica. Se aè null, quindi a.concat(b)genera un NullPointerExceptionma a+=btratterà il valore originale di acome se fosse null. Inoltre, il concat()metodo accetta solo Stringvalori mentre l' +operatore convertirà silenziosamente l'argomento in una stringa (usando il toString()metodo per gli oggetti). Così laconcat() metodo è più rigoroso in ciò che accetta.

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

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

Ora smonta con javap -c(incluso nel Sun JDK). Dovresti vedere un elenco 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."<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

Quindi, a += bè l'equivalente di

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

Il concatmetodo dovrebbe essere più veloce. Tuttavia, con più stringhe il StringBuildermetodo vince, almeno in termini di prestazioni.

Il codice sorgente di Stringe StringBuilder(e la sua classe base privata pacchetto) è disponibile in src.zip di Sun JDK. Puoi vedere che stai creando un array di caratteri (ridimensionandolo se necessario) e poi lo butti via quando crei il finale String. In pratica, l'allocazione della memoria è sorprendentemente veloce.

Aggiornamento: come osserva Pawel Adamski, le prestazioni sono cambiate negli HotSpot più recenti. javacproduce ancora esattamente lo stesso codice, ma il compilatore bytecode tradisce. I test semplici falliscono del tutto perché l'intero corpo del codice viene eliminato. La somma System.identityHashCode(non String.hashCode) mostra che il StringBuffercodice ha un leggero vantaggio. Soggetto 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 .


4
@HyperLink Puoi vedere il codice usando javap -csu una classe compilata che lo usa. (Oh, come nella risposta. Devi solo interpretare lo smontaggio del bytecode, che non dovrebbe essere così difficile.)
Tom Hawtin - affronta il

1
È possibile consultare le specifiche JVM per comprendere i singoli bytecode. Le cose che vorresti fare riferimento sono nel capitolo 6. Un po 'oscuro, ma puoi prenderne abbastanza facilmente le idee.
Hot Licks,

1
Mi chiedo perché il compilatore Java usi StringBuilderanche quando si uniscono due stringhe? Se Stringinclusi metodi statici per concatenare fino a quattro stringhe o tutte le stringhe in a String[], il codice potrebbe aggiungere fino a quattro stringhe con due allocazioni di oggetti (il risultato Stringe il relativo supporto char[], né uno ridondante) e un numero qualsiasi di stringhe con tre allocazioni ( il String[], il risultato Stringe il supporto char[], con solo il primo ridondante). Allo stato attuale, l'utilizzo StringBuilderrichiederà al massimo quattro allocazioni e richiederà la copia di ogni personaggio due volte.
supercat

Quell'espressione, a + = b. Non significa: a = a + b?
più venerabile signore,

3
Le cose sono cambiate da quando è stata creata questa risposta. Per favore leggi la mia risposta qui sotto.
Paweł Adamski,

90

Niyaz ha ragione, ma vale anche la pena notare che l'operatore speciale + può essere convertito in qualcosa di più efficiente dal compilatore Java. Java ha una classe StringBuilder che rappresenta una stringa mutabile non thread-safe. Quando si eseguono un sacco di concatenazioni di stringhe, il compilatore Java si converte in silenzio

String a = b + c + d;

in

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

che per stringhe di grandi dimensioni è significativamente più efficiente. Per quanto ne so, questo non accade quando si utilizza il metodo concat.

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

Quindi, se sei estremamente preoccupato per l'efficienza, allora dovresti usare il metodo concat quando concatenare stringhe possibilmente vuote e usare + altrimenti. Tuttavia, la differenza di prestazioni dovrebbe essere trascurabile e probabilmente non dovresti mai preoccuparti di questo.


concat infatti non lo fa. Ho modificato il mio post con una decompilazione del metodo concat
shsteimer,

10
infatti lo fa. Cerca le prime righe del tuo codice concat. Il problema con concat è che genera sempre una nuova stringa ()
Marcio Aguiar,

2
@MarcioAguiar: forse vuoi dire che + genera sempre un nuovo String- come dici tu, concatha un'eccezione quando concateni un vuoto String.
Blaisorblade,

45

Ho eseguito un test simile a @marcio ma con il seguente loop 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 anche lanciato StringBuilder.append(). Ogni test è stato eseguito 10 volte, con 100.000 ripetizioni per ogni test. Ecco i risultati:

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

Non ho ancora decompilato la classe per vedere gli interni o eseguirlo tramite il profiler, ma sospetto che a += btrascorra molto del tempo a creare nuovi oggetti StringBuildere poi riconvertirli String.


4
Il tempo di creazione dell'oggetto conta davvero. Ecco perché in molte situazioni utilizziamo StringBuilder direttamente anziché sfruttare StringBuilder dietro +.
coolcfan,

1
@coolcfan: quando +viene usato per due stringhe, ci sono casi in cui l'uso StringBuilderè migliore di quanto sarebbe String.valueOf(s1).concat(s2)? Qualche idea sul perché i compilatori non userebbero quest'ultimo [oppure ometterebbero la valueOfchiamata nei casi in cui s1è noto che non è null]?
Supercat,

1
@supercat mi dispiace non lo so. Forse le persone che stanno dietro questo zucchero sono le migliori per rispondere a questo.
coolcfan,

25

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

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 = "!@#";
    }

    @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

Mi chiedo perché Java Stringnon abbia mai incluso una funzione statica per formare una stringa concatenando gli elementi di a String[]. L'utilizzo +per concatenare 8 stringhe utilizzando una tale funzione richiederebbe la costruzione e successivamente l'abbandono String[8], ma sarebbe l'unico oggetto che dovrebbe essere costruito abbandonato, mentre l'utilizzo StringBuilderrichiederebbe la costruzione e l'abbandono StringBuilderdell'istanza e almeno un char[]archivio di supporto.
supercat

@supercat Alcuni String.join()metodi statici sono stati aggiunti in Java 8, come wrapper di sintassi rapida intorno alla java.util.StringJoinerclasse.
Ti Strga

@TiStrga: la gestione di è +cambiata per utilizzare tali funzioni?
supercat

@supercat Ciò romperebbe la compatibilità binaria all'indietro, quindi no. Era solo in risposta al tuo "perché String mai incluso una funzione statica" commento: ora c'è è una tale funzione. Il resto della tua proposta (refactoring +per usarla) richiederebbe più di quello che gli sviluppatori Java sono disposti a cambiare, purtroppo.
Ti Strga

@TiStrga: c'è un modo in cui un file bytecode Java può indicare "Se la funzione X è disponibile, chiamala; altrimenti fai qualcos'altro" in un modo che potrebbe essere risolto nel processo di caricamento di una classe? La generazione di codice con un metodo statico che può concatenarsi al metodo statico di Java oppure utilizzare un generatore di stringhe se non è disponibile sembrerebbe la soluzione ottimale.
supercat

22

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

Tuttavia, tutte le risposte finora stanno ignorando gli effetti delle ottimizzazioni di runtime di HotSpot. In particolare, 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 le moderne JVM questo non è un modo valido per profilare il codice.

Il motivo per cui l'ottimizzazione del runtime è importante è che molte di queste differenze nel codice - anche inclusa la creazione di oggetti - sono completamente diverse una volta che HotSpot inizia. L'unico modo per sapere con certezza è la profilazione del codice in situ .

Alla fine, tutti questi metodi sono 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!


Immagino che per "queste operazioni temporanee" si intenda l'uso dell'analisi di escape per allocare oggetti "heap" nello stack ove dimostrabili corretti. Sebbene l'analisi di escape sia presente in HotSpot (utile per rimuovere un po 'di sincronizzazione), non ci credo, al momento in cui scrivo, tu
Tom Hawtin - tackline

21

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

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

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

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

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


Ho testato su java jdk1.8.0_241 il tuo codice, per me il codice "a + b" sta dando risultati ottimizzati. Con concat (): 203ms e con "+": 113ms . Immagino che nella versione precedente non fosse così ottimizzato.
Akki,

6

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

  1. Se si utilizza il metodo concat , sarà possibile concatenare le stringhe solo nel caso dell'operatore + , è anche possibile concatenare la stringa con qualsiasi tipo di dati.

    Per esempio:

    String s = 10 + "Hello";

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

    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 che io concatichi 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 in questo modo:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy

    Caso 2:

    Ora concaturerò le stesse stringhe tramite l' 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 concatichiamo le stringhe tramite l' operatore + , mantiene una classe 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 :)


Mia cara, sai benissimo che qualsiasi stringa letterale trattata come un oggetto String stesso che memorizza nel pool String. Quindi in questo caso abbiamo 4 letterali stringhe. Quindi, ovviamente, almeno 4 oggetti dovrebbero essere creati nel pool.
Deepak Sharma,

1
Non credo: String s="I"+"am"+"good"+"boy"; String s2 = "go".concat("od"); System.out.println(s2 == s2.intern());stampe true, il che significa che "good"non era nel pool di stringhe prima di chiamareintern()
fabian

Sto parlando solo di questa riga String s = "I" + "am" + "good" + "boy"; In questo caso, tutti e 4 sono valori letterali di stringhe conservati in un pool. Pertanto, 4 oggetti devono essere creati in pool.
Deepak Sharma,

4

Per completezza, ho voluto aggiungere che la definizione dell'operatore "+" è disponibile in JLS SE8 15.18.1 :

Se solo un'espressione di operando è di tipo String, la conversione di stringa (§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 oggetto 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'oggetto String viene appena creato (§12.5) a meno che l'espressione non sia costante (§15.28).

A proposito dell'implementazione, JLS dice 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 oggetto String intermedio. Per aumentare le prestazioni della concatenazione di stringhe ripetute, un compilatore Java può utilizzare la classe 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 oggetto wrapper convertendo direttamente da un tipo primitivo in una stringa.

Quindi, a giudicare da "un compilatore Java può usare la classe StringBuffer o una tecnica simile per ridurre", compilatori diversi potrebbero produrre diversi byte-code.


2

L' operatore + può lavorare tra una stringa e un valore di tipo di dati stringa, carattere, intero, doppio o float. Converte semplicemente il valore nella sua rappresentazione di stringa prima della concatenazione.

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

Tranne questo, il codice che hai fornito fa le stesse cose.


2

Io non la penso così.

a.concat(b)è implementato in String e penso che l'implementazione non sia cambiata molto dai primi java machine. L' +implementazione dell'operazione dipende dalla versione e dal compilatore Java. Attualmente +è implementato usando StringBufferper rendere l'operazione il più veloce possibile. Forse in futuro, questo cambierà. Nelle versioni precedenti di java+ operazione su String era molto più lenta in quanto produceva risultati intermedi.

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


7
"Attualmente + è implementato utilizzando StringBuffer" False It's StringBuilder. StringBuffer è l'innesto thread-safe di StringBuilder.
Frederic Morin,

1
Prima di Java 1.5 era StringBuffer, poiché era la versione in cui StringBuilder fu introdotto per la prima volta.
ccpizza,

0

Quando si utilizza +, la velocità diminuisce all'aumentare della lunghezza della stringa, ma quando si utilizza concat, la velocità è più stabile e l'opzione migliore è utilizzare la classe StringBuilder che ha una velocità stabile per farlo.

Immagino che tu possa capire il perché. Ma il modo migliore per creare stringhe lunghe è usare StringBuilder () e append (), entrambe le velocità saranno inaccettabili.


1
l'utilizzo dell'operatore + equivale a utilizzare StringBuilder ( docs.oracle.com/javase/specs/jls/se8/html/… )
ihebiheb
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.