StringBuilder vs String-Verkettung in toString () in Java


925

Angesichts der 2 folgenden toString()Implementierungen, welche bevorzugt wird:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

oder

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}

?

Noch wichtiger ist, dass es angesichts der nur drei Eigenschaften keinen Unterschied macht, aber an welchem ​​Punkt würden Sie von +concat zu wechseln StringBuilder?


40
Ab wann wechseln Sie zu StringBuilder? Wenn es Speicher oder Leistung beeinflusst. Oder wenn es könnte. Wenn Sie dies wirklich nur einmal für ein paar Saiten tun, keine Sorge. Wenn Sie dies jedoch immer wieder tun, sollten Sie bei der Verwendung von StringBuilder einen messbaren Unterschied feststellen.
Ewall

Was ist der Mittelwert von 100 im Parameter?
Asif Mushtaq

2
@UnKnown 100 ist die anfängliche Größe von StringBuilder
Nicht-Sequitor

@nonsequitor Also werden maximal 100 Zeichen sein?
Asif Mushtaq

10
@Unbekannt Nein, nur die Anfangsgröße. Wenn Sie die ungefähre Größe der Zeichenfolge kennen, mit der Sie sich befassen, können Sie angeben, StringBuilderwie viel Größe im Voraus zugewiesen werden soll. Andernfalls muss die Größe verdoppelt werden, wenn der Speicherplatz knapp wird neues char[]Array kopieren dann Daten über - was teuer ist. Sie können betrügen, indem Sie die Größe angeben. Dann ist diese Array-Erstellung nicht erforderlich. Wenn Sie also glauben, dass Ihre Zeichenfolge ~ 100 Zeichen lang ist, können Sie den StringBuilder auf diese Größe einstellen und er muss intern nie erweitert werden.
Nicht-Sequitor

Antworten:


964

Version 1 ist vorzuziehen, da sie kürzer ist und der Compiler sie tatsächlich in Version 2 umwandelt - ohne Leistungsunterschied.

Noch wichtiger ist, dass wir nur drei Eigenschaften haben, die vielleicht keinen Unterschied machen. Aber zu welchem ​​Zeitpunkt wechseln Sie von Concat zu Builder?

An dem Punkt, an dem Sie in einer Schleife verketten - normalerweise kann der Compiler dies nicht ersetzen StringBuilder .


19
Das stimmt, aber die Sprachreferenz besagt auch, dass dies optional ist. Tatsächlich habe ich gerade einen einfachen Test mit JRE 1.6.0_15 durchgeführt und in der dekompilierten Klasse keine Compileroptimierung festgestellt.
Bruno Conde

37
Ich habe gerade den Code aus der Frage ausprobiert (kompiliert in JDK 1.6.0_16) und die Optimierung wie erwartet gefunden. Ich bin mir ziemlich sicher, dass alle modernen Compiler dies tun werden.
Michael Borgwardt

22
Du hast Recht. Wenn ich mir den Bytecode ansehe, sehe ich deutlich die StringBuilder-Optimierung. Ich habe einen Dekompiler verwendet und irgendwie konvertiert er wieder in concat. +1
Bruno Conde

80
Nicht um ein totes Pferd zu schlagen, aber der Wortlaut in der Spezifikation lautet: To increase the performance of repeated string concatenation, a Java compiler _may_ use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.Das Schlüsselwort, das es gibt, kann sein . Sollten wir uns nicht schützen, da dies offiziell optional ist (obwohl dies höchstwahrscheinlich implementiert ist)?
Lucas

93
@ Lucas: Nein, das sollten wir nicht. Wenn der Compiler beschließt, diese Optimierung nicht durchzuführen, liegt dies daran, dass es sich nicht lohnt. In 99% der Fälle weiß der Compiler besser, welche Optimierung sich lohnt, daher sollte der Entwickler als Faustregel nicht eingreifen. Natürlich Ihre Situation kann in die andere 1% fallen, aber das kann nur durch (vorsichtig) überprüft werden Benchmarking.
Sleske

255

Der Schlüssel ist, ob Sie eine einzelne Verkettung an einem Ort schreiben oder im Laufe der Zeit akkumulieren.

In dem von Ihnen angegebenen Beispiel macht es keinen Sinn, StringBuilder explizit zu verwenden. (Sehen Sie sich den kompilierten Code für Ihren ersten Fall an.)

Wenn Sie jedoch einen String erstellen, z. B. innerhalb einer Schleife, verwenden Sie StringBuilder.

Um dies zu verdeutlichen, gehen Sie davon aus, dass riesiges Array Tausende von Zeichenfolgen enthält: Code wie folgt:

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}

ist sehr zeit- und speicherverschwendend im Vergleich zu:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();

3
Ja, StringBuilder muss das String-Objekt nicht immer wieder neu erstellen.
Olga

124
Verdammt, ich habe diese 2-Funktion verwendet, um eine große Zeichenfolge zu testen, in der ich arbeite. 6.51min vs 11secs
user1722791

1
Übrigens können Sie auch verwenden result += s;(im ersten Beispiel)
RAnders00

1
Wie viele Objekte werden durch diese Anweisung erstellt? "{a:"+ a + ", b:" + b + ", c: " + c +"}";
Asif Mushtaq

1
Was ist mit so etwas wie: String str = (a == null)? null: a '+ (b == null)? null: b '+ (c == null)? c: c '+ ...; ? Verhindert dies die Optimierung?
Amitfr

75

Ich bevorzuge:

String.format( "{a: %s, b: %s, c: %s}", a, b, c );

... weil es kurz und lesbar ist.

Ich würde dies nicht für die Geschwindigkeit optimieren, wenn Sie es nicht in einer Schleife mit einer sehr hohen Wiederholungszahl verwenden und den Leistungsunterschied gemessen haben.

Ich bin damit einverstanden, dass dieses Formular verwirrend werden kann, wenn Sie viele Parameter ausgeben müssen (wie in einem der Kommentare angegeben). In diesem Fall würde ich zu einer besser lesbaren Form wechseln (möglicherweise mit ToStringBuilder von Apache-Commons - entnommen aus der Antwort von matt b) und die Leistung erneut ignorieren.


64
Es ist tatsächlich länger, enthält mehr Symbole und hat Variablen, deren Text nicht in der richtigen Reihenfolge ist.
Tom Hawtin - Tackline

4
Würden Sie sagen, dass es weniger lesbar ist als einer der anderen Ansätze?
Tangens

3
Ich schreibe dies lieber, weil es einfacher ist, mehr Variablen hinzuzufügen, aber ich bin nicht sicher, ob es besser lesbar ist - insbesondere, wenn die Anzahl der Argumente groß wird. Es funktioniert auch nicht in Zeiten, in denen Sie Bits zu unterschiedlichen Zeiten hinzufügen müssen.
Alex Feinman

78
Scheint schwerer zu lesen (für mich). Jetzt muss ich zwischen {...} und den Parametern hin und her scannen.
Steve Kuo

10
Ich bevorzuge dieses Formular, weil es sicher ist, wenn einer der Parameternull
rds

74

In den meisten Fällen sehen Sie keinen tatsächlichen Unterschied zwischen den beiden Ansätzen, aber es ist einfach, ein Worst-Case-Szenario wie dieses zu erstellen:

public class Main
{
    public static void main(String[] args)
    {
        long now = System.currentTimeMillis();
        slow();
        System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");

        now = System.currentTimeMillis();
        fast();
        System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
    }

    private static void fast()
    {
        StringBuilder s = new StringBuilder();
        for(int i=0;i<100000;i++)
            s.append("*");      
    }

    private static void slow()
    {
        String s = "";
        for(int i=0;i<100000;i++)
            s+="*";
    }
}

Die Ausgabe ist:

slow elapsed 11741 ms
fast elapsed 7 ms

Das Problem ist, dass das Anhängen von + = an eine Zeichenfolge eine neue Zeichenfolge rekonstruiert, sodass etwas kostet, das linear zur Länge Ihrer Zeichenfolgen ist (Summe aus beiden).

Also - zu Ihrer Frage:

Der zweite Ansatz wäre schneller, aber weniger lesbar und schwieriger zu warten. Wie gesagt, in Ihrem speziellen Fall würden Sie den Unterschied wahrscheinlich nicht sehen.


Vergessen Sie nicht .concat (). Ich würde vermuten, dass die verstrichene Zeit zwischen 10 und 18 ms liegt, was sie vernachlässigbar macht, wenn kurze Zeichenfolgen wie im ursprünglichen Post-Beispiel verwendet werden.
Droo

9
Während Sie Recht haben +=, war das ursprüngliche Beispiel eine Folge von +, die der Compiler in einen einzelnen string.concatAufruf umwandelt . Ihre Ergebnisse gelten nicht.
Blindy

1
@Blindy & Droo: - Sie haben beide Recht. Die Verwendung von .concate in einem solchen Szenario ist die beste Problemumgehung, da + = jedes Mal, wenn eine Schleifenroutine ausgeführt wird, ein neues Objekt erstellt.
Perilbrain

3
Weißt du, dass sein toString () nicht in einer Schleife aufgerufen wird?
Omry Yadan

Ich habe dieses Beispiel versucht, um die Geschwindigkeit zu testen. Meine Ergebnisse sind also: langsam verstrichene 29672 ms; schnell verstrichene 15 ms. Die Antwort liegt also auf der Hand. Aber wenn es 100 Iterationen wären - die Zeit ist die gleiche - 0 ms. Wenn 500 Iterationen - 16 ms und 0 ms. Und so weiter.
Ernestas Gruodis

28

Ich hatte auch einen Konflikt mit meinem Chef über die Tatsache, ob Append oder + verwendet werden soll. Da sie Append verwenden (ich kann es immer noch nicht herausfinden, wie sie sagen, jedes Mal, wenn ein neues Objekt erstellt wird). Also dachte ich daran, F & E zu machen. Obwohl ich die Erklärung von Michael Borgwardt liebe, wollte ich nur eine Erklärung zeigen, wenn jemand in Zukunft wirklich etwas wissen muss.

/**
 *
 * @author Perilbrain
 */
public class Appc {
    public Appc() {
        String x = "no name";
        x += "I have Added a name" + "We May need few more names" + Appc.this;
        x.concat(x);
        // x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
        //System.out.println(x);
    }

    public void Sb() {
        StringBuilder sbb = new StringBuilder("no name");
        sbb.append("I have Added a name");
        sbb.append("We May need few more names");
        sbb.append(Appc.this);
        sbb.append(sbb.toString());
        // System.out.println(sbb.toString());
    }
}

und Demontage der oben genannten Klasse kommt als heraus

 .method public <init>()V //public Appc()
  .limit stack 2
  .limit locals 2
met001_begin:                                  ; DATA XREF: met001_slot000i
  .line 12
    aload_0 ; met001_slot000
    invokespecial java/lang/Object.<init>()V
  .line 13
    ldc "no name"
    astore_1 ; met001_slot001
  .line 14

met001_7:                                      ; DATA XREF: met001_slot001i
    new java/lang/StringBuilder //1st object of SB
    dup
    invokespecial java/lang/StringBuilder.<init>()V
    aload_1 ; met001_slot001
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    ldc "I have Added a nameWe May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    aload_0 ; met001_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    astore_1 ; met001_slot001
  .line 15
    aload_1 ; met001_slot001
    aload_1 ; met001_slot001
    invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
    pop
  .line 18
    return //no more SB created
met001_end:                                    ; DATA XREF: met001_slot000i ...

; ===========================================================================

;met001_slot000                                ; DATA XREF: <init>r ...
    .var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001                                ; DATA XREF: <init>+6w ...
    .var 1 is x Ljava/lang/String; from met001_7 to met001_end
  .end method
;44-1=44
; ---------------------------------------------------------------------------


; Segment type: Pure code
  .method public Sb()V //public void Sb
  .limit stack 3
  .limit locals 2
met002_begin:                                  ; DATA XREF: met002_slot000i
  .line 21
    new java/lang/StringBuilder
    dup
    ldc "no name"
    invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    astore_1 ; met002_slot001
  .line 22

met002_10:                                     ; DATA XREF: met002_slot001i
    aload_1 ; met002_slot001
    ldc "I have Added a name"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 23
    aload_1 ; met002_slot001
    ldc "We May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 24
    aload_1 ; met002_slot001
    aload_0 ; met002_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    pop
  .line 25
    aload_1 ; met002_slot001
    aload_1 ; met002_slot001
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 28
    return
met002_end:                                    ; DATA XREF: met002_slot000i ...


;met002_slot000                                ; DATA XREF: Sb+25r
    .var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001                                ; DATA XREF: Sb+9w ...
    .var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
  .end method
;96-49=48
; ---------------------------------------------------------------------------

Anhand der beiden obigen Codes können Sie sehen, dass Michael Recht hat. In jedem Fall wird nur ein SB-Objekt erstellt.


27

Seit Java 1.5 generiert die einfache einzeilige Verkettung mit "+" und StringBuilder.append () genau den gleichen Bytecode.

Verwenden Sie aus Gründen der Lesbarkeit des Codes "+".

2 Ausnahmen:

  • Multithread-Umgebung: StringBuffer
  • Verkettung in Schleifen: StringBuilder / StringBuffer

22

Unter Verwendung der neuesten Version von Java (1.8) zeigt die Disassembly ( javap -c) die vom Compiler eingeführte Optimierung. +Auch sb.append()wird sehr ähnlicher Code generiert. Es lohnt sich jedoch, das Verhalten zu überprüfen, wenn wir es +in einer for-Schleife verwenden.

Hinzufügen von Zeichenfolgen mit + in einer for-Schleife

Java:

public String myCatPlus(String[] vals) {
    String result = "";
    for (String val : vals) {
        result = result + val;
    }
    return result;
}

ByteCode :( forSchleifenauszug)

12: iload         5
14: iload         4
16: if_icmpge     51
19: aload_3
20: iload         5
22: aaload
23: astore        6
25: new           #3                  // class java/lang/StringBuilder
28: dup
29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload         6
38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc          5, 1
48: goto          12

Hinzufügen von Zeichenfolgen mit stringbuilder.append

Java:

public String myCatSb(String[] vals) {
    StringBuilder sb = new StringBuilder();
    for(String val : vals) {
        sb.append(val);
    }
    return sb.toString();
}

ByteCdoe :( forSchleifenauszug)

17: iload         5
19: iload         4
21: if_icmpge     43
24: aload_3
25: iload         5
27: aaload
28: astore        6
30: aload_2
31: aload         6
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc          5, 1
40: goto          17
43: aload_2

Es gibt jedoch einen kleinen Unterschied . Im ersten Fall, in dem +verwendet wurde, StringBuilderwird für jede Schleifeniteration eine neue erstellt, und das generierte Ergebnis wird durch Ausführen eines toString()Aufrufs (29 bis 41) gespeichert . Sie generieren also Zwischenzeichenfolgen, die Sie bei Verwendung des +Operators in der forSchleife wirklich nicht benötigen .


1
Ist das Oracle JDK oder OpenJDK?
Christophe Roussy

12

In Java 9 sollte die Version 1 schneller sein, da sie in invokedynamiccall konvertiert wird . Weitere Details finden Sie in JEP-280 :

Die Idee ist, den gesamten StringBuilder-Append-Tanz durch einen einfachen aufgerufenen dynamischen Aufruf von java.lang.invoke.StringConcatFactory zu ersetzen, der die Werte akzeptiert, die verkettet werden müssen.


9

Aus Leistungsgründen wird von der Verwendung von +=( StringVerkettung) abgeraten. Der Grund dafür ist: Java Stringist unveränderlich. Jedes Mal, wenn eine neue Verkettung durchgeführt wird, wird eine neue Stringerstellt (die neue hat einen anderen Fingerabdruck als die ältere, die sich bereits im String-Pool befindet ). Das Erstellen neuer Zeichenfolgen setzt den GC unter Druck und verlangsamt das Programm: Die Objekterstellung ist teuer.

Der folgende Code sollte es gleichzeitig praktischer und klarer machen.

public static void main(String[] args) 
{
    // warming up
    for(int i = 0; i < 100; i++)
        RandomStringUtils.randomAlphanumeric(1024);
    final StringBuilder appender = new StringBuilder();
    for(int i = 0; i < 100; i++)
        appender.append(RandomStringUtils.randomAlphanumeric(i));

    // testing
    for(int i = 1; i <= 10000; i*=10)
        test(i);
}

public static void test(final int howMany) 
{
    List<String> samples = new ArrayList<>(howMany);
    for(int i = 0; i < howMany; i++)
        samples.add(RandomStringUtils.randomAlphabetic(128));

    final StringBuilder builder = new StringBuilder();
    long start = System.nanoTime();
    for(String sample: samples)
        builder.append(sample);
    builder.toString();
    long elapsed = System.nanoTime() - start;
    System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);

    String accumulator = "";
    start = System.nanoTime();
    for(String sample: samples)
        accumulator += sample;
    elapsed = System.nanoTime() - start;
    System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);

    start = System.nanoTime();
    String newOne = null;
    for(String sample: samples)
        newOne = new String(sample);
    elapsed = System.nanoTime() - start;
    System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}

Die Ergebnisse für einen Lauf sind unten angegeben.

builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us

builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us

builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us

builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us

builder - 10000 - elapsed: 3364us 
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us

Ohne Berücksichtigung der Ergebnisse für 1 Verkettung (JIT hat seine Aufgabe noch nicht erfüllt), ist selbst für 10 Verkettungen die Leistungsbeeinträchtigung relevant. Bei Tausenden von Verkettungen ist der Unterschied enorm.

Lehren aus diesem sehr schnellen Experiment (mit dem obigen Code leicht reproduzierbar): Verwenden Sie das niemals, um +=Zeichenfolgen miteinander zu verketten, selbst in sehr einfachen Fällen, in denen einige Verkettungen erforderlich sind (wie gesagt, das Erstellen neuer Zeichenfolgen ist ohnehin teuer und übt Druck auf die aus GC).


9

Siehe das folgende Beispiel:

static final int MAX_ITERATIONS = 50000;
static final int CALC_AVG_EVERY = 10000;

public static void main(String[] args) {
    printBytecodeVersion();
    printJavaVersion();
    case1();//str.concat
    case2();//+=
    case3();//StringBuilder
}

static void case1() {
    System.out.println("[str1.concat(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str = str.concat(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case2() {
    System.out.println("[str1+=str2]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str += UUID.randomUUID() + "---";
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case3() {
    System.out.println("[str1.append(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    StringBuilder str = new StringBuilder("");
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str.append(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");

}

static void saveTime(List<Long> executionTimes, long startTime) {
    executionTimes.add(System.currentTimeMillis() - startTime);
    if (executionTimes.size() % CALC_AVG_EVERY == 0) {
        out.println("average time for " + executionTimes.size() + " concatenations: "
                + NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(() -> 0))
                + " ms avg");
        executionTimes.clear();
    }
}

Ausgabe:

Java-Bytecode-Version: 8
java.version: 1.8.0_144
[str1.concat (str2)]
durchschnittliche Zeit für 10000 Verkettungen: 0,096 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,185 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,327 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,501 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,656 ms durchschnittlich
Erstellte Zeichenfolge mit einer Länge von 1950000 in 17745 ms
[str1 + = str2]
durchschnittliche Zeit für 10000 Verkettungen: 0,21 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,652 ms
durchschnittliche Zeit für 10000 Verkettungen: 1,129 ms durchschnittliche Zeit für 10000 Verkettungen: 2,302 ms durchschnittlich
durchschnittliche Zeit für 10000 Verkettungen: 1,727 ms durchschnittlich

Erstellte Zeichenfolge mit einer Länge von 1950000 in 60279 ms
[str1.append (str2)]
durchschnittliche Zeit für 10000 Verkettungen: 0,002 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,002 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,002 ms
durchschnittliche Zeit für 10000 Verkettungen:
Durchschnittliche Zeit von 0,002 ms für 10000 Verkettungen: durchschnittlich 0,002 ms
Erstellte Zeichenfolge mit einer Länge von 1950000 in 100 ms

Wenn die Zeichenfolgenlänge zunimmt, nimmt auch die Verkettungszeit zu.
Hier wird das StringBuilderdefinitiv gebraucht.
Wie Sie sehen, UUID.randomUUID()+"---"wirkt sich die Verkettung: nicht wirklich auf die Zeit aus.

PS: Ich denke nicht, dass die Verwendung von StringBuilder in Java wirklich ein Duplikat davon ist.
Diese Frage spricht darüber, toString()welche meistens keine Verkettungen von großen Zeichenfolgen ausführt.


Update 2019

Seit java8einiger Zeit haben sich die Dinge ein wenig geändert. Es scheint, dass jetzt (java13) die Verkettungszeit von +=praktisch dieselbe ist wie str.concat(). Die StringBuilderVerkettungszeit ist jedoch immer noch konstant . (Der ursprüngliche Beitrag oben wurde leicht bearbeitet, um eine ausführlichere Ausgabe hinzuzufügen.)

Java-Bytecode-Version: 13
java.version: 13.0.1
[str1.concat (str2)]
durchschnittliche Zeit für 10000 Verkettungen: 0,047 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,1 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,17 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,255 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,336 ms durchschnittlich
Erstellte Zeichenfolge mit einer Länge von 1950000 in 9147 ms
[str1 + = str2]
durchschnittliche Zeit für 10000 Verkettungen: 0,037 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,097 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,249 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,298 ms durchschnittlich
durchschnittliche Zeit für 10000 Verkettungen: durchschnittlich 0,326 ms
Erstellte Zeichenfolge mit einer Länge von 1950000 in 10191 ms
[str1.append (str2)]
durchschnittliche Zeit für 10000 Verkettungen: 0,001 ms
durchschnittliche Zeit für 10000 Verkettungen: 0,001 ms
durchschnittliche Zeit für 10000 Verkettungen: Durchschnittliche
durchschnittliche Zeit von 0,001 ms für 10000 Verkettungen: Durchschnittliche
durchschnittliche Zeit von 0,001 ms für 10000 Verkettungen: durchschnittlich 0,001 ms
Erstellte Zeichenfolge mit einer Länge von 1950000 in 43 ms

Bemerkenswert bytecode:8/java.version:13ist, dass auch die Kombination einen guten Leistungsvorteil gegenüber hatbytecode:8/java.version:8


Dies sollte die akzeptierte Antwort sein. Es hängt von der Größe des String-Streams ab, die die Wahl von concat oder StringBuilder bestimmt
user1428716

@ user1428716 FYI: Antwort aktualisiert mit Ergebnissen, von java13denen jetzt abweichen. Aber ich denke, die Hauptschlussfolgerung bleibt dieselbe.
Marinos An

7

Apache Commons-Lang verfügt über eine ToStringBuilder- Klasse, die sehr einfach zu verwenden ist. Es macht einen guten Job, sowohl die Append-Logik zu handhaben als auch zu formatieren, wie Ihr toString aussehen soll.

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}

Gibt eine Ausgabe zurück, die aussieht com.blah.YourClass@abc1321f[a=whatever, b=foo].

Oder in einer kondensierteren Form durch Verketten:

public void toString() {
     return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}

Oder wenn Sie die Reflexion verwenden möchten, um jedes Feld der Klasse einzuschließen:

public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

Sie können auch den Stil des ToString anpassen, wenn Sie möchten.


4

Machen Sie die toString-Methode so lesbar wie möglich!

Die einzige Ausnahme in meinem Buch ist, wenn Sie mir beweisen können , dass es erhebliche Ressourcen verbraucht :) (Ja, dies bedeutet Profilerstellung)

Beachten Sie auch, dass der Java 5-Compiler schnelleren Code generiert als der handgeschriebene "StringBuffer" -Ansatz, der in früheren Java-Versionen verwendet wurde. Wenn Sie "+" verwenden, sind diese und zukünftige Verbesserungen kostenlos.


3

Es scheint eine Debatte darüber zu geben, ob die Verwendung von StringBuilder bei aktuellen Compilern noch erforderlich ist. Also dachte ich, ich gebe meine 2 Cent Erfahrung.

Ich habe eine JDBCErgebnismenge von 10.000 Datensätzen (ja, ich benötige alle in einem Stapel). Die Verwendung des Operators + dauert auf meinem Computer mit ca. 5 Minuten Java 1.8. Die Verwendung stringBuilder.append("")dauert für dieselbe Abfrage weniger als eine Sekunde.

Der Unterschied ist also riesig. Innerhalb einer Schleife StringBuilderist viel schneller.


2
Ich denke, in der Debatte geht es darum, es außerhalb von Schleifen zu verwenden. Ich denke, es gibt einen Konsens, den Sie brauchen, um ihn innerhalb einer Schleife zu verwenden.
Jordanien

2

In Bezug auf die Leistung ist die Verkettung von Strings mit '+' teurer, da eine völlig neue Kopie von String erstellt werden muss, da Strings in Java unveränderlich sind. Dies spielt eine besondere Rolle, wenn die Verkettung sehr häufig ist, z. B. innerhalb einer Schleife. Folgendes ist, was meine IDEE vorschlägt, wenn ich versuche, so etwas zu tun:

Geben Sie hier die Bildbeschreibung ein

Allgemeine Regeln:

  • Innerhalb einer einzelnen Zeichenfolgenzuweisung ist die Verwendung der Zeichenfolgenverkettung in Ordnung.
  • Wenn Sie eine Schleife erstellen, um einen großen Block von Zeichendaten aufzubauen, wählen Sie StringBuffer.
  • Die Verwendung von + = für einen String ist immer weniger effizient als die Verwendung eines StringBuffers. Daher sollten Warnglocken läuten. In bestimmten Fällen ist die erzielte Optimierung jedoch im Vergleich zu den Lesbarkeitsproblemen vernachlässigbar. Verwenden Sie daher Ihren gesunden Menschenverstand.

Hier ist ein schöner Jon Skeet Blog zu diesem Thema.


2
Sie sollten StringBuffer niemals verwenden, es sei denn, Sie benötigen unbedingt einen synchronisierten Zugriff von mehreren Threads. Ansonsten bevorzugen Sie StringBuilder, der nicht synchronisiert ist und daher weniger Overhead hat.
Erki der Loony


1

Folgendes habe ich in Java8 überprüft

  • Verwenden der String-Verkettung
  • Verwenden von StringBuilder

    long time1 = System.currentTimeMillis();
    usingStringConcatenation(100000);
    System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms");
    
    time1 = System.currentTimeMillis();
    usingStringBuilder(100000);
    System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms");
    
    
    private static void usingStringBuilder(int n)
    {
        StringBuilder str = new StringBuilder();
        for(int i=0;i<n;i++)
            str.append("myBigString");    
    }
    
    private static void usingStringConcatenation(int n)
    {
        String str = "";
        for(int i=0;i<n;i++)
            str+="myBigString";
    }

Es ist wirklich ein Albtraum, wenn Sie die Verkettung von Zeichenfolgen für eine große Anzahl von Zeichenfolgen verwenden.

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

-1

Ich denke, wir sollten mit dem StringBuilder-Ansatz zum Anhängen gehen. Grund dafür ist :

  1. Die String-Verkettung erstellt jedes Mal ein neues String-Objekt (da String ein unveränderliches Objekt ist), sodass 3 Objekte erstellt werden.

  2. Mit dem String Builder wird nur ein Objekt erstellt [StringBuilder ist veränderbar] und der weitere String wird daran angehängt.


Warum wird diese Antwort abgelehnt? docs.oracle.com/javase/8/docs/api/java/util/stream/… - Mutable Reduction
user1428716

-4

Für solche einfachen Saiten bevorzuge ich

"string".concat("string").concat("string");

In der Reihenfolge würde ich sagen, dass die bevorzugte Methode zum Erstellen eines Strings die Verwendung von StringBuilder, String # concat () und dann des überladenen Operators + ist. StringBuilder ist eine signifikante Leistungssteigerung, wenn große Zeichenfolgen bearbeitet werden, genau wie die Verwendung des Operators + eine starke Leistungsminderung darstellt (exponentiell starke Abnahme mit zunehmender Zeichenfolgengröße). Das einzige Problem bei der Verwendung von .concat () ist, dass NullPointerExceptions ausgelöst werden können.


9
Die Verwendung von concat () ist wahrscheinlich schlechter als '+', da das JLS die Konvertierung von '+' in einen StringBuilder ermöglicht und höchstwahrscheinlich alle JVMs dies tun oder eine effizientere Alternative verwenden - dies gilt wahrscheinlich auch nicht concat, das in Ihrem Beispiel mindestens eine vollständige Zwischenzeichenfolge erstellen und verwerfen muss.
Lawrence Dol
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.