String concatenation: Concat() vs operator "+"

Zakładając ciąg a i b:

a += b
a = a.concat(b)
Pod maską, czy to to samo?

Tutaj jest concat dekompilowany jako odniesienie. Chciałbym być w stanie dekompilować operator +, aby zobaczyć, co to robi.

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);
    }
}
Author: Peter Mortensen, 2008-09-06

11 answers

Nie, niezupełnie.

Po pierwsze, jest niewielka różnica w semantyce. Jeśli a jest null, to a.concat(b) rzuca NullPointerException, ale a+=b potraktuje oryginalną wartość a tak, jakby była null. Co więcej, metoda concat() akceptuje tylko wartości String, podczas gdy operator + po cichu przekonwertuje argument na łańcuch znaków (używając metody toString() dla obiektów). Więc metoda concat() jest bardziej rygorystyczna w tym, co akceptuje.

Aby zajrzeć pod maskę, napisz prostą klasę z a += b;

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

Teraz demontować z javap -c (zawarte w Sun JDK). Powinieneś zobaczyć listę zawierającą:

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

Więc a += b jest odpowiednikiem

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

Metoda concat powinna być szybsza. Jednak przy większej liczbie ciągów metoda StringBuilder wygrywa, przynajmniej pod względem wydajności.

Kod źródłowy String i StringBuilder (oraz jego Klasa package-private base) jest dostępny w src.zip Of The Sun JDK. Możesz zobaczyć, że tworzysz tablicę znaków (zmiana rozmiaru w razie potrzeby), a następnie wyrzucenie go podczas tworzenia ostatecznego String. W praktyce alokacja pamięci jest zaskakująco szybka.

Aktualizacja: jak zauważa Paweł Adamski, wydajność zmieniła się w nowszym hotspocie. javac nadal produkuje dokładnie ten sam kod, ale kompilator kodu bajtowego oszukuje. Proste testowanie całkowicie zawodzi, ponieważ cały kod jest wyrzucany. Sumowanie System.identityHashCode (Nie String.hashCode) pokazuje, że kod StringBuffer ma niewielką przewagę. Może ulec zmianie, gdy następny aktualizacja zostanie wydana lub jeśli używasz innego JVM. From @lukaseder, lista hotspot JVM intrinsics .

 489
Author: Tom Hawtin - tackline,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-12 12:57:00

Niyaz jest poprawny, ale warto również zauważyć, że operator special + może być przekształcony w coś bardziej wydajnego przez kompilator Javy. Java posiada klasę StringBuilder, która reprezentuje bezpieczny dla wątków, zmienny Łańcuch znaków. Po wykonaniu kilku konkatenacji, kompilator Javy po cichu konwertuje

String a = b + c + d;

Do

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

Który dla dużych strun jest znacznie bardziej wydajny. Z tego co wiem, to nie dzieje się tak, gdy używasz concat metoda.

Jednak metoda concat jest bardziej efektywna podczas łączenia pustego łańcucha z istniejącym łańcuchem. W takim przypadku JVM nie musi tworzyć nowego obiektu String i może po prostu zwrócić istniejący. Zobacz dokumentację concat , Aby to potwierdzić.

Więc jeśli jesteś bardzo zaniepokojony wydajnością, powinieneś użyć metody concat podczas łączenia możliwie-pustych łańcuchów i użyć + w przeciwnym razie. Jednak różnica w wydajności powinna być i pewnie nie powinieneś się o to martwić.

 82
Author: Eli Courtwright,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:31

Przeprowadziłem podobny test jak @marcio, ale z następującą pętlą:

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
}

Na dobrą sprawę, ja też dorzuciłem StringBuilder.append(). Każdy test był prowadzony 10 razy, z 100k powtórzeń dla każdego biegu. Oto wyniki:

  • StringBuilder wygrywa. Wynik zegara wynosił 0 dla większości biegów, a najdłuższy trwał 16ms.
  • a += b zajmuje około 40000ms (40s) na każdy bieg.
  • concat wymaga tylko 10000ms (10s) na run.

Nie dekompilowałem Klasa, aby zobaczyć wewnętrzne lub uruchomić go przez profiler, ale podejrzewam, że a += b spędza dużo czasu na tworzeniu nowych obiektów StringBuilder, a następnie konwertowaniu ich z powrotem do String.

 42
Author: ckpwong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-06 19:25:12

Tom ma rację opisując dokładnie to, co robi operator+. Tworzy tymczasowe StringBuilder, dołącza części i kończy się toString().

Jednak wszystkie odpowiedzi do tej pory ignorują efekty optymalizacji czasu pracy hotspot. W szczególności te tymczasowe operacje są rozpoznawane jako wspólny wzorzec i zastępowane bardziej wydajnym kodem maszynowym w czasie wykonywania.

@marcio: stworzyłeś mikro-benchmark ; z nowoczesnymi JVM to nie jest prawidłowy sposób na kod profilu.

Powodem, dla którego optymalizacja czasu pracy ma znaczenie, jest to, że wiele z tych różnic w kodzie-nawet w tym tworzenie obiektów-jest zupełnie inne, gdy HotSpot zacznie działać. Jedynym sposobem na upewnienie się jest profilowanie kodu in situ.

Wreszcie, wszystkie te metody są niesamowicie szybkie. Może to być przypadek przedwczesnej optymalizacji. Jeśli masz kod, który dużo łączy ciągi, sposób na uzyskanie maksymalnej prędkości prawdopodobnie nie ma nic wspólnego z jakimi operatorami wybierasz, a zamiast tego algorytm, którego używasz!
 22
Author: Jason Cohen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-06 19:19:52

Co powiesz na proste testy? Użyto poniższego kodu:

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);
  • Wersja "a + b" wykonana w 2500ms.
  • a.concat(b) wykonane w 1200ms.
Testowane kilka razy. Wykonanie wersji concat() zajęło średnio połowę czasu.

Ten wynik zaskoczył mnie, ponieważ metoda concat() zawsze tworzy nowy łańcuch znaków (zwraca "new String(result)". Wiadomo, że:

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

Dlaczego kompilator nie był w stanie zoptymalizować ciągu tworzenie w kodzie "a + b", znając zawsze ten sam ciąg znaków? To może uniknąć tworzenia nowego ciągu. Jeśli nie wierzysz w powyższe stwierdzenie, przetestuj dla siebie.

 19
Author: Marcio Aguiar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-10 20:55:32

Większość odpowiedzi pochodzi z 2008 roku. Wygląda na to, że rzeczy zmieniły się na przestrzeni czasu. Moje najnowsze benchmarki wykonane za pomocą JMH pokazują, że w Javie 8 + jest około dwa razy szybszy niż concat.

Mój benchmark:

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

Wyniki:

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
 16
Author: Paweł Adamski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-29 08:56:14

Zasadniczo istnieją dwie istotne różnice między metodą + a metodą concat.

  1. Jeśli używasz metody concat, wtedy będziesz mógł łączyć łańcuchy tylko wtedy, gdy w przypadku + operator, można również połączyć łańcuch z dowolnym typem danych.

    Na Przykład:

    String s = 10 + "Hello";
    

    W tym przypadku wyjście powinno być 10Hello.

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

    W powyższym przypadku musisz podać dwa ciągi znaków obowiązkowe.

  2. Druga i główna różnica między + i concat to:

    Przypadek 1: Załóżmy, że łączę te same Ciągi z operatorem concat w ten sposób

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

    W tym przypadku całkowita liczba obiektów utworzonych w Puli wynosi 7 w ten sposób:

    I
    am
    good
    boy
    Iam
    Iamgood
    Iamgoodboy
    

    Case 2:

    Teraz będę konkatinował te same struny poprzez + operator

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

    W powyższym przypadku całkowita liczba obiektów utworzonych jest tylko 5.

    Faktycznie, kiedy konkatinujemy struny poprzez + operator następnie utrzymuje klasę StringBuffer do wykonania tego samego zadania w następujący sposób: -

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

    W ten sposób stworzy tylko pięć obiektów.

Więc chłopaki to są podstawowe różnice między + oraz metodą concat . Enjoy :)

 4
Author: Deepak Sharma,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-05 21:15:53

Dla kompletności chciałem dodać, że definicję operatora " + " można znaleźć w JLS SE8 15.18.1:

Jeżeli tylko jedno wyrażenie operandowe jest typu String, to string konwersja (§5.1.11) jest wykonywana na drugim operandie w celu wytworzenia / align = "left" /

Wynik konkatenacji łańcuchowej jest odniesieniem do obiektu Łańcuchowego to jest konkatenacja dwóch ciągów operandowych. Postacie z lewej strony operand poprzedzają znaki prawej ręki operand w nowo utworzonym smyczku.

Obiekt String jest nowo utworzony (§12.5), chyba że wyrażenie jest wyrażenie stałe (§15.28).

O implementacji JLS mówi co następuje:

implementacja może wybrać konwersję i konkatenację w jednym kroku, aby uniknąć tworzenia, a następnie odrzucenia pośredniego Obiekt typu String. Aby zwiększyć wydajność powtarzanego ciągu concatenation, kompilator Javy może używać klasy StringBuffer lub podobna technika zmniejszania liczby pośrednich obiektów łańcuchowych które powstają w wyniku oceny wyrażenia.

W przypadku typów prymitywnych implementacja może również zoptymalizować tworzenie obiektu wrappera poprzez konwersję bezpośrednio z prymitywnego wpisz Do ciągu znaków.

Więc sądząc po ' kompilator Javy może używać klasy StringBuffer lub podobnej techniki do reduce', różne Kompilatory mogą produkować inny kod bajtowy.

 2
Author: dingalapadum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-26 15:00:44

Operator + może pracować między łańcuchem znaków a wartością typu danych string, char, integer, double lub float. Po prostu konwertuje wartość na jej reprezentację łańcuchową przed konkatenacją.

Operator concat może być wykonywany tylko na i z łańcuchami. Sprawdza zgodność typu danych i wyświetla błąd, jeśli nie pasują.

Poza tym, podany kod robi to samo.

 2
Author: Niyaz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-01 18:03:27

Nie sądzę.

a.concat(b) jest zaimplementowany w String i myślę, że implementacja niewiele się zmieniła od wczesnych maszyn java. Implementacja operacji + zależy od wersji Javy i kompilatora. Obecnie + jest zaimplementowany przy użyciu StringBuffer aby operacja była jak najszybsza. Może w przyszłości to się zmieni. We wcześniejszych wersjach Javy + działanie na łańcuchach znaków było znacznie wolniejsze, ponieważ dawało wyniki pośrednie.

Myślę, że += jest zaimplementowane przy użyciu + i podobnie zoptymalizowane.

 2
Author: Bartosz Bierkowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-27 14:46:12

Przy użyciu+, prędkość maleje wraz ze wzrostem długości łańcucha, ale przy użyciu concat, prędkość jest bardziej stabilna, a najlepszą opcją jest użycie klasy StringBuilder, która ma stabilną prędkość w tym celu.

Chyba rozumiesz dlaczego. Ale całkowicie najlepszym sposobem tworzenia długich łańcuchów jest użycie StringBuilder () i append (), obie prędkości będą niedopuszczalne.

 0
Author: iamreza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-05 21:18:03