1121 Stimmen

StringBuilder gegen String-Konkatenation in toString() in Java

Angesichts der beiden unten stehenden toString()-Implementierungen, welche ist bevorzugt:

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 wir nur 3 Eigenschaften haben und es keinen Unterschied machen könnte, aber ab wann würden Sie von der Verwendung von + für die Konkatenierung zu StringBuilder wechseln?

63 Stimmen

An welchem Punkt wechseln Sie zu StringBuilder? Wenn es sich auf den Speicher oder die Leistung auswirkt. Oder wenn es sein könnte. Wenn Sie dies wirklich nur einmal für ein paar Zeichenfolgen tun, keine Sorgen. Aber wenn Sie es immer wieder tun werden, sollten Sie einen messbaren Unterschied feststellen, wenn Sie StringBuilder verwenden.

1 Stimmen

Was bedeutet 100 als Parameter?

3 Stimmen

@Unbekannt 100 ist die Anfangsgröße des StringBuilder

29voto

perilbrain Punkte 7722

Ich hatte auch einen Konflikt mit meinem Chef darüber, ob append oder + verwendet werden soll. Da sie Append verwenden (ich kann immer noch nicht verstehen, da sie jedes Mal sagen, dass ein neues Objekt erstellt wird). Also dachte ich, etwas Recherche zu betreiben. Obwohl ich Michaels Borgwardt Erklärung liebe, wollte ich nur eine Erklärung zeigen, wenn jemand es wirklich in Zukunft wissen muss.

/**
 *
 * @author Perilbrain
 */
public class Appc {
    public Appc() {
        String x = "kein Name";
        x += "Ich habe einen Namen hinzugefügt" + "Wir brauchen vielleicht noch ein paar Namen" + Appc.this;
        x.concat(x);
        // x+=x.toString(); --Es erstellt ein neues StringBuilder-Objekt vor der Verkettung, also vermeiden Sie es, wenn möglich
        //System.out.println(x);
    }

    public void Sb() {
        StringBuilder sbb = new StringBuilder("kein Name");
        sbb.append("Ich habe einen Namen hinzugefügt");
        sbb.append("Wir brauchen vielleicht noch ein paar Namen");
        sbb.append(Appc.this);
        sbb.append(sbb.toString());
        // System.out.println(sbb.toString());
    }
}

und die Disassemblierung der obigen Klasse ergibt

 .method public ()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.()V
  .line 13
    ldc "kein 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.()V
    aload_1 ; met001_slot001
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    ldc "Ich habe einen Namen hinzugefügtWir brauchen vielleicht noch ein paar Namen"
    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: r ...
    .var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001                                ; DATA XREF: +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 "kein Name"
    invokespecial java/lang/StringBuilder.(Ljava/lang/String;)V
    astore_1 ; met002_slot001
  .line 22

met002_10:                                     ; DATA XREF: met002_slot001i
    aload_1 ; met002_slot001
    ldc "Ich habe einen Namen hinzugefügt"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 23
    aload_1 ; met002_slot001
    ldc "Wir brauchen vielleicht noch ein paar Namen"
    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
; ---------------------------------------------------------------------------

Aus den obigen beiden Codes können Sie sehen, dass Michael recht hat. In jedem Fall wird nur ein SB-Objekt erstellt.

23voto

ring bearer Punkte 19605

Bei Verwendung der neuesten Version von Java(1.8) zeigt die Disassembly(javap -c) die Optimierung, die vom Compiler eingeführt wurde. Sowohl + als auch sb.append() werden sehr ähnlichen Code generieren. Es wird jedoch sinnvoll sein, das Verhalten zu überprüfen, wenn wir + in einer for-Schleife verwenden.

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

Java:

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

ByteCode:(for-Schleifen-Auszug)

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."":()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 Strings mit stringbuilder.append

Java:

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

ByteCode:(for-Schleifen-Auszug)

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 auffälligen Unterschied. Im ersten Fall, in dem + verwendet wurde, wird für jede For-Schleifen-Iteration ein neuer StringBuilder erstellt und das generierte Ergebnis wird durch Aufruf von toString() gespeichert (29 bis 41). Sie generieren also Zwischenzeichenfolgen, die Sie wirklich nicht benötigen, wenn Sie den +-Operator in einer for-Schleife verwenden.

1 Stimmen

Ist dies Oracle JDK oder OpenJDK ?

1 Stimmen

@ChristopheRoussy spielt keine Rolle, da sie genau den gleichen Code bestehen.

0 Stimmen

@Holger, laut Heinz Kabutz: "OpenJDK ist zu 99% der gleiche Code wie Oracle JDK (abhängig davon, von welchem Anbieter Sie ihn erhalten), also läuft es im Grunde darauf hinaus, Support zu erhalten". Nicht sicher, wo diese 1% liegen oder ob das noch stimmt.

18voto

ZhekaKozlov Punkte 32454

In Java 9 sollte die Version 1 schneller sein, da sie in einen invokedynamic-Aufruf umgewandelt wird. Weitere Details finden Sie unter JEP-280:

Die Idee besteht darin, den gesamten StringBuilder-Anhangstanz durch einen einfachen invokedynamic-Aufruf an java.lang.invoke.StringConcatFactory zu ersetzen, der die Werte akzeptiert, die konkateniert werden müssen.

9voto

Paolo Maresca Punkte 6898

Zu Leistungszwecken wird die Verwendung von += (String-Konkatenation) nicht empfohlen. Der Grund dafür ist: Java String ist unveränderlich, jedes Mal, wenn eine neue Verkettung erfolgt, wird ein neuer String erstellt (der neue hat einen anderen Fingerabdruck als der bereits im String-Pool vorhandene). Das Erstellen neuer Strings belastet den GC und verlangsamt das Programm: Objekterstellung ist teuer.

Der folgende Code sollte es praktischer und gleichzeitig klarer machen.

public static void main(String[] args) 
{
    // Aufwärmen
    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));

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

public static void test(final int howMany) 
{
    List 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 eines Durchlaufs werden unten angezeigt.

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

Nicht berücksichtigt werden die Ergebnisse für 1 Verkettung (JIT war noch nicht aktiv), selbst für 10 Verkettungen ist die Leistungseinbuße relevant; bei Tausenden von Verkettungen ist der Unterschied riesig.

Erkenntnisse aus diesem sehr schnellen Experiment (mit dem obigen Code leicht reproduzierbar): Verwenden Sie niemals +=, um Strings zusammenzufügen, selbst in sehr einfachen Fällen, in denen einige Verkettungen erforderlich sind (wie gesagt, das Erstellen neuer Strings ist sowieso teuer und belastet den GC).

7voto

matt b Punkte 135206

Apache Commons-Lang verfügt über eine ToStringBuilder-Klasse, die super einfach zu verwenden ist. Sie erledigt die Append-Logik sowie das Formatieren dessen, wie Ihr toString aussehen soll, sehr gut.

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

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

Oder in einer kompakteren Form unter Verwendung von Verkettung:

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, falls gewünscht.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X