589 Stimmen

String-Verkettung: concat() vs. "+"-Operator

Angenommen, String a und b:

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

Sind sie unter der Haube dasselbe?

Hier ist concat als Referenz dekompiliert. Ich würde gerne die Dekompilierung der + Operator, um zu sehen, was das bewirkt.

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

647voto

Tom Hawtin - tackline Punkte 142461

Nein, nicht ganz.

Erstens gibt es einen kleinen semantischen Unterschied. Wenn a est null dann a.concat(b) wirft eine NullPointerException sondern a+=b wird der ursprüngliche Wert von a als wäre es null . Außerdem ist die concat() Methode akzeptiert nur String Werte, während die + Operator wandelt das Argument stillschweigend in eine Zeichenkette um (unter Verwendung der toString() Methode für Objekte). Daher ist die concat() Methode ist strenger in Bezug auf das, was sie annimmt.

Um einen Blick unter die Haube zu werfen, schreiben Sie eine einfache Klasse mit a += b;

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

Demontieren Sie nun mit javap -c (im Sun JDK enthalten). Sie sollten eine Auflistung sehen, die Folgendes enthält:

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

Also, a += b ist das Äquivalent zu

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

El concat Methode sollte schneller sein. Bei mehr Zeichenketten wird jedoch die StringBuilder Methode gewinnt, zumindest was die Leistung angeht.

Der Quellcode von String y StringBuilder (und seine paketprivate Basisklasse) ist in src.zip des Sun JDK verfügbar. Sie können sehen, dass Sie ein Char-Array aufbauen (und die Größe nach Bedarf ändern) und es dann wegwerfen, wenn Sie die endgültige String . In der Praxis ist die Speicherzuweisung erstaunlich schnell.

Aktualisierung: Wie Pawel Adamski feststellt, hat sich die Leistung in neueren HotSpot. javac erzeugt immer noch genau denselben Code, aber der Bytecode-Compiler schummelt. Einfache Tests schlagen völlig fehl, weil der gesamte Code weggeworfen wird. Summieren System.identityHashCode (nicht String.hashCode ) zeigt die StringBuffer Code hat einen leichten Vorteil. Dies kann sich ändern, wenn das nächste Update veröffentlicht wird oder wenn Sie eine andere JVM verwenden. Von @lukaseder , eine Liste von HotSpot JVM-Intrinsics .

103voto

Eli Courtwright Punkte 174547

Niyaz ist richtig, aber es ist auch erwähnenswert, dass der spezielle +-Operator durch den Java-Compiler in etwas Effizienteres umgewandelt werden kann. Java verfügt über eine StringBuilder-Klasse, die einen nicht thread-sicheren, veränderbaren String darstellt. Wenn man eine Reihe von String-Verkettungen durchführt, konvertiert der Java-Compiler stillschweigend

String a = b + c + d;

in

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

was bei großen Zeichenketten wesentlich effizienter ist. Soweit ich weiß, geschieht dies nicht, wenn Sie die concat-Methode verwenden.

Die concat-Methode ist jedoch effizienter, wenn eine leere Zeichenkette mit einer vorhandenen Zeichenkette verknüpft werden soll. In diesem Fall muss die JVM kein neues String-Objekt erstellen und kann einfach das vorhandene zurückgeben. Siehe die concat-Dokumentation um dies zu bestätigen.

Wenn Sie also sehr auf Effizienz bedacht sind, sollten Sie die concat-Methode verwenden, wenn Sie möglicherweise leere Strings verketten, und ansonsten + verwenden. Der Leistungsunterschied sollte jedoch vernachlässigbar sein, und Sie sollten sich wahrscheinlich nie Gedanken darüber machen.

52voto

ckpwong Punkte 2019

Ich habe einen ähnlichen Test wie @marcio durchgeführt, allerdings mit der folgenden Schleife:

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
}

Nur um sicherzugehen, habe ich Folgendes hinzugefügt StringBuilder.append() ebenfalls. Jeder Test wurde 10-mal durchgeführt, mit 100k Wiederholungen für jeden Lauf. Hier sind die Ergebnisse:

  • StringBuilder gewinnt haushoch. Bei den meisten Durchläufen war die Zeit 0, und der längste dauerte 16 ms.
  • a += b dauert etwa 40000ms (40s) für jeden Durchlauf.
  • concat benötigt nur 10000ms (10s) pro Durchlauf.

Ich habe die Klasse noch nicht dekompiliert, um die Interna zu sehen oder sie durch den Profiler laufen zu lassen, aber ich vermute a += b verbringt einen Großteil der Zeit damit, neue Objekte der StringBuilder und konvertiert sie dann zurück in String .

38voto

Paweł Adamski Punkte 3005

Die meisten Antworten hier sind aus dem Jahr 2008. Es sieht so aus, als hätten sich die Dinge im Laufe der Zeit geändert. Meine neuesten Benchmarks mit JMH zeigen, dass bei Java 8 + ist etwa zwei Mal schneller als concat .

Meine Messlatte:

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

Ergebnisse:

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

22voto

Jason Cohen Punkte 78227

Tom hat genau beschrieben, was der Operator + bewirkt. Er erzeugt eine temporäre StringBuilder an, fügt die Teile an und schließt ab mit toString() .

Bei allen bisherigen Antworten wurden jedoch die Auswirkungen der HotSpot-Laufzeitoptimierungen außer Acht gelassen. Insbesondere werden diese temporären Operationen als gängiges Muster erkannt und zur Laufzeit durch effizienteren Maschinencode ersetzt.

@marcio: Sie haben ein Mikro-Benchmark Mit modernen JVMs ist dies kein gültiger Weg, um Code zu profilieren.

Der Grund für die Laufzeitoptimierung ist, dass viele dieser Unterschiede im Code - einschließlich der Objekterstellung - völlig anders sind, sobald HotSpot in Gang kommt. Die einzige Möglichkeit, dies mit Sicherheit festzustellen, ist die Profilerstellung Ihres Codes in situ .

Und schließlich sind alle diese Methoden tatsächlich unglaublich schnell. Dies könnte ein Fall von verfrühter Optimierung sein. Wenn Sie Code haben, der häufig Zeichenketten verkettet, hat der Weg zur maximalen Geschwindigkeit wahrscheinlich nichts damit zu tun, welche Operatoren Sie wählen, sondern mit dem Algorithmus, den Sie verwenden!

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