StringBuffer vs StringBuilder

0

Witam

Ostatnio podczas pisania pod Netbeans'em aplikacji która przetwarzała większe ilości tekstu, wyskoczył mi warning - że można zastąpić StringBuffer StringBuilderem.
Szybka wizyta na Google wykazała, że StringBuffer jest threadsafe, StringBuilder nie.

I tutaj pytanie - czy ktoś może wie, jak to się ma do wydajności? Skoro StringBuffer jest threadsafe to jest pewnie wolniejszy - chodzi mi tutaj przede wszystkim czy ktoś kiedykolwiek próbował łączyć większe ilości tekstu i zastąpił Buffer Builderem i zanotował, jaki przyrost wydajności to dało.

0

Napisałem kiedyś programik testowy do porównywania prędkości różnych sposobów sklejania Stringów. StringBuffer jest średnio 2 razy wolniejszy niż StringBuider i stosunek czasów nie zależy od ilości iteracji. Konkatenacja Stringów (operator +) jest dramatycznie wolniejsza (tysiące razy).

0

Generalnie według dokumentacji, jedno i drugie to "to samo" w przenośni, jednak różni się synchronizacją. Dlatego też, jeżeli nie bawisz się w wielowątkowe sklejanie stringów, zawsze używaj string buildera. Na potwierdzenie moich słów cytacik

As of release JDK 5, this class has been supplemented with an equivalent class designed for use by a single thread, StringBuilder.The StringBuilder class should generally be used in preference to this one, as it supports all of the same operations but it is faster, as it performs no synchronization.

i znowu potwierdzenie tego co napisałem - różnica polega tylko na synchronizacji - co jest czasochłonne. Pozdro!
@bogdans
A patrzyłeś jak to się ma do zjadania pamięci, alokacji char[] i wycieków? Aż mnie to zaciekawiło :)

0

Żadnego profilera nie używałem, windowsowy TaskManager nie wykazywał żadnego zauważalnego zwiększenia zużycia pamięci, np. podczas wykonywania takiej pętli

String zwrot="";
for (int i=1;i<=1000000;i++)
{
     zwrot+="A";
}
2

Łatwy sposób sprawdzenia:

class StringTest {
	public String toString() {
		String zwrot = "";
		for (int i = 1; i <= 1000000; i++)
			zwrot += "A";
		return zwrot;
	}
}

Kompilujemy, a następnie wydajemy polecenie:
javap -c StringTest
Zobaczymy coś takiego:

class StringTest {
  StringTest();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public java.lang.String toString();
    Code:
       0: ldc           #2                  // String 
       2: astore_1      
       3: iconst_1      
       4: istore_2      
       5: iload_2       
       6: ldc           #3                  // int 1000000
       8: if_icmpgt     37
      11: new           #4                  // class java/lang/StringBuilder
      14: dup           
      15: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
      18: aload_1       
      19: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: ldc           #7                  // String A
      24: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      27: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      30: astore_1      
      31: iinc          2, 1
      34: goto          5
      37: aload_1       
      38: areturn       
}

Widać, że kompilator użył klasy StringBuilder. Ale nie jest na tyle inteligentny, aby wyciągnąć tworzenie obiektu tej klasy przed pętlę.

0

Będę precyzyjny, u mnie było tak:

...
int iteracje = Integer.parseInt(args[0])
...
powiel1("A",iteracje);
...
powiel3("A",iteracje);
...
private static String powiel1(String s,int ile)
{
    String zwrot="";
    for (int i=1;i<=ile;i++)
    {
       zwrot+=s;
    }
    return zwrot;
}
private static String powiel3(String s,int ile)
{
    StringBuilder zwrot=new StringBuilder("");
    for (int i=1;i<=ile;i++)
    {
       zwrot.append(s);
    }
    return zwrot.toString();
}

i program był wywołany z parametrem 1000 000.

0

Przede wszystkim referencyjny JVM od Javy 6 ma wbudowany Lock Elision: http://www.ibm.com/developerworks/java/library/j-jtp10185/index.html i może sam omijać synchronizację w pewnych przypadkach. Aby mieć pewność co do wpływu synchronizacji na szybkość pasowałoby porównać StringBuildera i StringBuffera na jakimś JVMie, który nie wspiera Lock Elision. Może Dalvik nie ma?

1 użytkowników online, w tym zalogowanych: 0, gości: 1