StringBuilder vs String concatenation w toString () w Javie

925

Biorąc pod uwagę 2 toString()poniższe implementacje, które są preferowane:

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

lub

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

?

Co ważniejsze, biorąc pod uwagę, że mamy tylko 3 właściwości, może to nie mieć znaczenia, ale w którym momencie +przeszedłbyś z concat na StringBuilder?

niesekwencyjny
źródło
40
W którym momencie przełączasz się na StringBuilder? Gdy wpływa na pamięć lub wydajność. Lub kiedy może. Jeśli naprawdę robisz to tylko dla kilku ciągów, nie martw się. Ale jeśli zamierzasz to robić w kółko, powinieneś zobaczyć mierzalną różnicę podczas korzystania z StringBuilder.
ewall
jaka jest średnia parametru 100?
Asif Mushtaq
2
@UnKnown 100 jest początkowym rozmiarem StringBuilder
niesekwencyjnym
@nonsequitor Czyli maksymalna liczba znaków to 100?
Asif Mushtaq
10
@Nieznany nie tylko rozmiar początkowy, jeśli znasz przybliżony rozmiar ciągu, z którym masz do czynienia, możesz powiedzieć, StringBuilderjaki rozmiar przydzielić z góry, w przeciwnym razie, jeśli zabraknie miejsca, będzie musiał podwoić rozmiar, tworząc nowa char[]tablica następnie kopiuje dane - co jest kosztowne. Możesz oszukiwać, podając rozmiar, a wtedy nie ma potrzeby tworzenia tablicy - więc jeśli uważasz, że Twój łańcuch będzie miał ~ 100 znaków, możesz ustawić StringBuilder na ten rozmiar i nigdy nie będzie on musiał się rozszerzać wewnętrznie.
niesekwencjonowany

Odpowiedzi:

964

Wersja 1 jest preferowana, ponieważ jest krótsza, a kompilator faktycznie zmieni ją w wersję 2 - bez różnicy wydajności.

Co ważniejsze, ponieważ mamy tylko 3 właściwości, może to nie mieć znaczenia, ale w którym momencie przełączasz się z konkat na konstruktor?

W miejscu łączenia w pętli - zwykle wtedy kompilator nie może StringBuildersam się zastąpić .

Michael Borgwardt
źródło
19
To prawda, ale odniesienie językowe stwierdza również, że jest to opcjonalne. W rzeczywistości właśnie wykonałem prosty test z JRE 1.6.0_15 i nie widziałem żadnej optymalizacji kompilatora w zdekompilowanej klasie.
bruno conde
37
Właśnie wypróbowałem kod z pytania (skompilowany na JDK 1.6.0_16) i znalazłem optymalizację zgodnie z oczekiwaniami. Jestem pewien, że wszystkie nowoczesne kompilatory to zrobią.
Michael Borgwardt,
22
Masz rację. Patrząc na kod bajtowy, wyraźnie widzę optymalizację StringBuilder. Używałem dekompilatora i, w pewnym sensie, konwertuje on z powrotem do konkat. +1
bruno conde
80
Nie po to, aby pokonać martwego konia, ale sformułowanie w specyfikacji brzmi: 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.kluczowe słowo może istnieć . Biorąc pod uwagę, że jest to oficjalnie opcjonalne (choć najprawdopodobniej wdrożone), czy nie powinniśmy się chronić?
Lucas
93
@Lucas: Nie, nie powinniśmy. Jeśli kompilator zdecyduje się nie wykonywać tej optymalizacji, dzieje się tak, ponieważ nie jest tego wart. W 99% przypadków kompilator lepiej wie, która optymalizacja jest tego warta, więc deweloper nie powinien ingerować. Oczywiście twoja sytuacja może spaść do pozostałych 1%, ale można to sprawdzić tylko poprzez (ostrożne) testy porównawcze.
sleske
255

Kluczem jest to, czy piszesz pojedynczą konkatenację w jednym miejscu, czy gromadzisz ją z czasem.

W podanym przykładzie nie ma sensu jawnie używać StringBuilder. (Spójrz na skompilowany kod pierwszego przypadku.)

Ale jeśli budujesz ciąg np. Wewnątrz pętli, użyj StringBuilder.

Aby to wyjaśnić, zakładając, że hugeArray zawiera tysiące ciągów, koduje się tak:

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

jest bardzo marnujący czas i pamięć w porównaniu z:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();
joel.neely
źródło
3
Tak, StringBuilder nie musi ciągle tworzyć obiektu String.
Olga,
124
Cholera, użyłem tych 2 funkcji do przetestowania dużego ciągu, w którym pracuję. 6,51min vs 11secs
user1722791
1
Nawiasem mówiąc, możesz również użyć result += s;(w pierwszym przykładzie)
RAnders00
1
ile obiektów zostanie utworzonych przez to zdanie? "{a:"+ a + ", b:" + b + ", c: " + c +"}";
Asif Mushtaq
1
Co powiesz na: String str = (a == null)? null: a '+ (b == null)? null: b '+ (c == null)? c: c '+ ...; ? Czy to zapobiegnie optymalizacji?
amitfr
75

Wolę:

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

... ponieważ jest krótki i czytelny.

Chciałbym nie zoptymalizować pod kątem szybkości, chyba go używać wewnątrz pętli z bardzo dużą liczbą powtórzeń i zmierzyli różnicę wydajności.

Zgadzam się, że jeśli musisz podać wiele parametrów, ten formularz może być mylący (jak mówi jeden z komentarzy). W tym przypadku przerzuciłbym się na bardziej czytelną formę (być może używając ToStringBuilder z apache-commons - wzięte z odpowiedzi Matt b) i ponownie zignorowałem wydajność.

tangens
źródło
64
Jest właściwie dłuższy, zawiera więcej symboli i zawiera zmienne, których tekst jest poza kolejnością.
Tom Hawtin - tackline
4
Czy powiedziałbyś, że jest mniej czytelny niż jedno z innych podejść?
tangens
3
Wolę to pisać, ponieważ łatwiej jest dodać więcej zmiennych, ale nie jestem pewien, czy jest bardziej czytelny - zwłaszcza, że ​​liczba argumentów staje się duża. Nie działa również w sytuacjach, gdy trzeba dodawać bity w różnych momentach.
Alex Feinman,
78
Wydaje się trudniejszy do odczytania (dla mnie). Teraz muszę skanować tam iz powrotem między {...} a parametrami.
Steve Kuo,
10
Wolę tę formę do, ponieważ jest bezpieczny, jeśli jeden z parametrów jestnull
RDS
74

W większości przypadków nie widać rzeczywistej różnicy między tymi dwoma podejściami, ale łatwo jest stworzyć najgorszy scenariusz, taki jak ten:

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+="*";
    }
}

Dane wyjściowe to:

slow elapsed 11741 ms
fast elapsed 7 ms

Problem polega na tym, że dodanie + = do łańcucha rekonstruuje nowy łańcuch, więc kosztuje coś liniowego do długości łańcuchów (suma obu).

Więc - na twoje pytanie:

Drugie podejście byłoby szybsze, ale jest mniej czytelne i trudniejsze w utrzymaniu. Jak powiedziałem, w twoim konkretnym przypadku prawdopodobnie nie zobaczysz różnicy.

Omry Yadan
źródło
Nie zapomnij o .concat (). Przypuszczam, że czas, który upłynął, wynosi od 10 do 18 ms, co czyni go nieistotnym przy użyciu krótkich ciągów znaków, takich jak oryginalny przykład postu.
Droo,
9
Chociaż masz rację +=, oryginalny przykład był sekwencją +, którą kompilator przekształca w pojedyncze string.concatwywołanie. Twoje wyniki nie mają zastosowania.
Blindy
1
@Blindy & Droo: - Oboje macie rację. Najlepszym rozwiązaniem jest użycie .concate w takim scenariuszu, ponieważ + = tworzy nowy obiekt za każdym razem, gdy wykonywana jest procedura pętli.
perilbrain
3
czy wiesz, że jego toString () nie jest wywoływany w pętli?
Omry Yadan
Próbowałem tego przykładu, aby przetestować prędkość. Więc moje wyniki to: wolno upłynęło 29672 ms; szybko upłynęło 15 ms. Więc odpowiedź jest oczywista. Ale jeśli byłoby to 100 iteracji - czas jest taki sam - 0 ms. Jeśli 500 iteracji - 16 ms i 0 ms. I tak dalej.
Ernestas Gruodis
28

Miałem też spór z szefem o to, czy użyć append, czy +. Ponieważ używają Append (wciąż nie mogę się domyślić, jak mówią za każdym razem, gdy tworzony jest nowy obiekt). Pomyślałem więc, żeby zrobić kilka prac badawczo-rozwojowych. Chociaż uwielbiam wyjaśnienia Michaela Borgwardta, ale chciałem tylko wyjaśnić, czy ktoś naprawdę będzie musiał wiedzieć w przyszłości.

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

i demontaż wyższej klasy wychodzi jako

 .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
; ---------------------------------------------------------------------------

Z powyższych dwóch kodów widać, że Michael ma rację. W każdym przypadku tworzony jest tylko jeden obiekt SB.

perilbrain
źródło
27

Od wersji Java 1.5 proste łączenie jednowierszowe za pomocą „+” i StringBuilder.append () generuje dokładnie ten sam kod bajtowy.

Dlatego ze względu na czytelność kodu użyj „+”.

2 wyjątki:

  • środowisko wielowątkowe: StringBuffer
  • konkatenacja w pętlach: StringBuilder / StringBuffer
Zofren
źródło
22

W najnowszej wersji Javy (1.8) dezasemblacja ( javap -c) pokazuje optymalizację wprowadzoną przez kompilator. +również sb.append()wygeneruje bardzo podobny kod. Warto jednak sprawdzić zachowanie, jeśli korzystamy +z pętli for.

Dodawanie ciągów za pomocą + w pętli for

Jawa:

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

ByteCode :( forfragment pętli)

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

Dodawanie ciągów za pomocą stringbuilder.append

Jawa:

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

ByteCdoe :( forfragment pętli)

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

Jest jednak rażąca różnica . W pierwszym przypadku, gdy +został użyty, StringBuilderdla każdej iteracji pętli tworzony jest nowy, a wygenerowany wynik jest zapisywany przez wykonanie toString()wywołania (29–41). Generujesz więc łańcuchy pośrednie, których tak naprawdę nie potrzebujesz podczas używania +operatora w forpętli.

posiadacz pierścienia
źródło
1
Czy to Oracle JDK czy OpenJDK?
Christophe Roussy,
12

W Javie 9 wersja 1 powinna być szybsza, ponieważ jest konwertowana na invokedynamicwywołanie. Więcej informacji można znaleźć w JEP-280 :

Chodzi o to, aby zastąpić cały taniec dodawania StringBuilder prostym wywołaniem dynamicznym wywołanym do java.lang.invoke.StringConcatFactory, który zaakceptuje wartości wymagające konkatenacji.

ZhekaKozlov
źródło
9

Ze względu na wydajność odradza się stosowanie +=( Stringkonkatenacji). Powód jest następujący: Java Stringjest niezmienna, za każdym razem, gdy wykonywana jest nowa konkatenacja, Stringtworzony jest nowy (nowy ma inny odcisk palca od starszego już w puli ciągów ). Tworzenie nowych ciągów naciska na GC i spowalnia program: tworzenie obiektów jest kosztowne.

Poniższy kod powinien uczynić go bardziej praktycznym i czytelnym jednocześnie.

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

Wyniki dla biegu podano poniżej.

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

Nie biorąc pod uwagę wyników dla 1 konkatenacji (JIT jeszcze nie wykonał swojej pracy), nawet dla 10 konkatenacji kara za wyniki jest istotna; dla tysięcy konkatenacji różnica jest ogromna.

Wnioski wyciągnięte z tego bardzo szybkiego eksperymentu (łatwego do odtworzenia z powyższym kodem): nigdy nie używaj +=do konkatenacji ciągów razem, nawet w bardzo podstawowych przypadkach, gdy potrzebnych jest kilka konkatenacji (jak powiedziano, tworzenie nowych ciągów jest i tak drogie i wywiera presję na GC).

Paolo Maresca
źródło
9

Zobacz przykład poniżej:

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

Wynik:

java bytecode version: 8
java.version: 1.8.0_144
[str1.concat (str2)]
średni czas dla 10000 konkatenacji: 0,096 ms
średni czas dla 10000 konkatenacji: 0,185 ms
średni czas dla 10000 konkatenacji: 0,327 ms
średni czas dla 10000 konkatenacji:
średni czas 0,501 ms dla 10000 konkatenacji: 0,656 ms średni
Utworzony ciąg długości: 1950000 w 17745 ms
[str1 + = str2]
średni czas dla 10000 konkatenacji: 0,21 ms
średni czas dla 10000 konkatenacji: 0,652 ms
średni czas dla 10000 konkatenacji: 1,129 ms śr.
Średni czas dla 10000 konkatenacji: 1,727 ms śr
średni czas dla 10000 konkatenacji: 2,302 ms średni
Utworzony ciąg długości: 1950000 w 60279 ms
[str1.append (str2)]
średni czas dla 10000 konkatenacji: 0,002 ms
średni średni czas dla 10000 konkatenacji: 0,002 ms
średni średni czas dla 10000 konkatenacji: 0,002 ms
średni średni czas dla 10000 konkatenacji: 0,002 ms
średni czas dla 10000 konkatenacji: 0,002 ms średni
Utworzony ciąg długości: 1950000 w 100 ms

Wraz ze wzrostem długości łańcucha rośnie czas konkatenacji.
To jest StringBuilderzdecydowanie potrzebne.
Jak widać, konkatenacja: UUID.randomUUID()+"---"tak naprawdę nie wpływa na czas.

PS: Nie sądzę, kiedy korzystanie z StringBuilder w Javie jest naprawdę duplikatem tego.
To pytanie mówi o tym, toString()które przez większość czasu nie wykonują konkatenacji wielkich łańcuchów.


Aktualizacja 2019

Od java8czasów wszystko się nieco zmieniło. Wydaje się, że teraz (java13) czas konkatenacji +=jest praktycznie taki sam jak str.concat(). Jednak StringBuilderczas konkatenacji jest wciąż stały . (Oryginalny post powyżej został nieco zmodyfikowany, aby dodać więcej pełnych wyników)

java bytecode version: 13
java.version: 13.0.1
[str1.concat (str2)]
średni czas dla 10000 konkatenacji: 0,047 ms
średni czas dla 10000 konkatenacji: 0,1 ms
średni czas dla 10000 konkatenacji: 0,17 ms
średni czas dla 10000 konkatenacji:
średni czas 0,255 ms dla 10000 konkatenacji: 0,366 ms średni
Utworzony ciąg długości: 1950000 w 9147 ms
[str1 + = str2]
średni czas dla 10000 konkatenacji: 0,037 ms
średni czas dla 10000 konkatenacji: 0,097 ms
średni czas dla
Średni czas 10000 konkatenacji: 0,249 ms średni czas 10000 konkatenacji: 0,298 ms
średni czas dla 10000 konkatenacji: 0,326 ms śr.
Utworzony ciąg długości: 1950000 w 10191 ms
[str1.append (str2)]
średni czas dla 10000 konkatenacji: 0,001 ms
średni średni czas dla 10000 konkatenacji: 0,001 ms
średni czas dla 10000 konkatenacji: 0,001 ms
średni średni czas dla 10000 konkatenacji: 0,001 ms
średni czas dla 10000 konkatenacji: 0,001 ms średni
Utworzony ciąg długości: 1950000 w 43 ms

Warto zauważyć, że bytecode:8/java.version:13kombinacja ma dobrą wydajność w porównaniu dobytecode:8/java.version:8

Marinos An
źródło
To powinna być zaakceptowana odpowiedź. Zależy to od rozmiaru Strumienia
Łańcuchowego,
@ user1428716 FYI: odpowiedź zaktualizowana o wyniki, z java13których teraz są inne. Ale myślę, że główny wniosek pozostaje ten sam.
Marinos An
7

Apache Commons-Lang ma klasę ToStringBuilder, która jest bardzo łatwa w użyciu. Wykonuje niezłą robotę zarówno w logice append, jak i formatowaniu tego, jak chcesz, aby twój toString wyglądał.

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

Zwraca wynik, który wygląda com.blah.YourClass@abc1321f[a=whatever, b=foo].

Lub w bardziej skondensowanej formie za pomocą łączenia:

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

Lub jeśli chcesz użyć refleksji, aby uwzględnić wszystkie pola klasy:

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

Możesz także dostosować styl ToString, jeśli chcesz.

matowy b
źródło
4

Spraw, aby metoda toString była jak najbardziej czytelna!

Jedynym wyjątkiem w mojej książce jest to, że możesz mi udowodnić , że pochłania ona znaczne zasoby :) (Tak, to znaczy profilowanie)

Zauważ też, że kompilator Java 5 generuje szybszy kod niż odręczne podejście „StringBuffer” używane we wcześniejszych wersjach Java. Jeśli użyjesz „+”, to i przyszłe rozszerzenia będą dostępne za darmo.

Thorbjørn Ravn Andersen
źródło
3

Wydaje się, że istnieje debata, czy użycie StringBuilder jest nadal potrzebne w obecnych kompilatorach. Pomyślałem więc, że dam 2 centy doświadczenia.

Mam JDBCzestaw wyników 10 000 rekordów (tak, potrzebuję ich wszystkich w jednej partii). Korzystanie z operatora + zajmuje około 5 minut na moim komputerze Java 1.8. Użycie stringBuilder.append("")zajmuje mniej niż sekundę dla tego samego zapytania.

Różnica jest więc ogromna. Wewnątrz pętli StringBuilderjest znacznie szybszy.

Wir
źródło
2
Myślę, że debata dotyczy korzystania z niej poza pętlami. Myślę, że istnieje konsensus, którego należy użyć w pętli.
Jordan
2

Pod względem wydajności Łączenie ciągów za pomocą „+” jest droższe, ponieważ musi utworzyć zupełnie nową kopię ciągu, ponieważ ciągi są niezmienne w Javie. Odgrywa to szczególną rolę, jeśli konkatenacja jest bardzo częsta, np .: wewnątrz pętli. Oto, co sugeruje moja IDEA, gdy próbuję zrobić coś takiego:

wprowadź opis zdjęcia tutaj

Główne zasady:

  • W przypadku przypisania pojedynczego łańcucha użycie konkatenacji ciągu znaków jest w porządku.
  • Jeśli zapętlasz się, aby zbudować duży blok danych znaków, wybierz StringBuffer.
  • Użycie + = na String zawsze będzie mniej wydajne niż użycie StringBuffer, więc powinno zadzwonić dzwonkiem ostrzegawczym - ale w niektórych przypadkach uzyskana optymalizacja będzie nieznaczna w porównaniu z problemami z czytelnością, więc kieruj się zdrowym rozsądkiem.

Oto miły blog Jona Skeeta na ten temat.

Sudip Bhandari
źródło
2
Nigdy nie powinieneś używać StringBuffer, chyba że absolutnie potrzebujesz zsynchronizowanego dostępu z wielu wątków. W przeciwnym razie wolisz StringBuilder, który nie jest zsynchronizowany, a zatem ma mniejszy narzut.
Erki der Loony
1

Czy mogę wskazać, że jeśli zamierzasz iterować kolekcję i używać StringBuilder, możesz wypróbować Apache Commons Lang i StringUtils.join () (w różnych smakach)?

Niezależnie od wydajności, zaoszczędzi Ci konieczności tworzenia StringBuilders i pętli, co wydaje się być milionowym czasem.

Brian Agnew
źródło
1

Oto, co sprawdziłem w Javie8

  • Korzystanie z konkatenacji ciągów
  • Korzystanie z 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";
    }

To naprawdę koszmar, jeśli używasz konkatenacji łańcuchów dla dużej liczby łańcuchów.

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms
nagendra547
źródło
-1

Myślę, że powinniśmy zastosować podejście dołączania StringBuilder. Powodem jest:

  1. Konkatenat String utworzy za każdym razem nowy obiekt string (ponieważ String jest obiektem niezmiennym), więc utworzy 3 obiekty.

  2. W Konstruktorze ciągów utworzony zostanie tylko jeden obiekt [StringBuilder jest mutowalny], a dalszy ciąg zostanie do niego dołączony.

VishalTambe
źródło
Dlaczego ta odpowiedź jest kwestionowana? docs.oracle.com/javase/8/docs/api/java/util/stream/… - Mutable Reduction
user1428716
-4

Do takich prostych łańcuchów wolę używać

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

Chciałbym powiedzieć, że preferowaną metodą konstruowania łańcucha jest użycie StringBuilder, String # concat (), a następnie przeciążonego operatora +. StringBuilder to znaczny wzrost wydajności, gdy praca z dużymi łańcuchami, podobnie jak użycie operatora +, oznacza duży spadek wydajności (wykładniczo duży spadek wraz ze wzrostem wielkości łańcucha). Jedynym problemem związanym z używaniem .concat () jest to, że może on zgłaszać wyjątki NullPointerExceptions.

Droo
źródło
9
Korzystanie z concat () może być gorsze niż „+”, ponieważ JLS pozwala na konwersję „+” na StringBuilder, a najprawdopodobniej wszystkie JVM robią to lub używają bardziej wydajnej alternatywy - to samo nie jest prawdą concat, który musi utworzyć i odrzucić co najmniej jeden pełny ciąg pośredni w twoim przykładzie.
Lawrence Dol