2011-01-10 17 views
30

Przeczytam "Lepsza, szybsza, jaśniejsza Java" (autor: Bruce Tate i Justin Gehtland) i jestem zaznajomiony z wymaganiami czytelności w zespołach typu zwinnego, takich jak Robert Martin omawia w swoich czystych książkowych książkach. W zespole, w którym obecnie jestem, powiedziano mi wyraźnie, że nie należy używać operatora +, ponieważ tworzy on dodatkowe (i niepotrzebne) obiekty tekstowe w czasie wykonywania.StringBuilder/StringBuffer vs. "+" Operator

Ale ten numer article, napisany w 2004 roku, mówi o tym, jak przydział obiektów wynosi około 10 instrukcji maszynowych. (w zasadzie za darmo)

Mówi także o tym, w jaki sposób GC pomaga również obniżyć koszty w tym środowisku.

Jaka jest rzeczywista wartość kompromisów między używaniem +, StringBuilder lub StringBuffer? (W moim przypadku jest to StringBuffer tylko dlatego, że jesteśmy ograniczeni do Javy 1.4.2.)

StringBuffer dla mnie skutkuje brzydkim, mniej czytelnym kodem, jak pokazuje kilka przykładów z książki Tate'a. I StringBuffer jest zsynchronizowany z wątkami, który wydaje się mieć własne koszty, które przeważają nad "niebezpieczeństwem" podczas używania operatora +.

Myśli/Opinie?

+2

Podobny queation tutaj http://stackoverflow.com/questions/4645020/when-to-use-stringbuilder-in-java – Navi

+0

możliwe duplikat [StringBuilder vs konkatenacji String w toString () w Javie] (http://stackoverflow.com/questions/1532461/stringbuilder-vs-string-concatenation-in-tostring-in-java) –

+0

Możliwy duplikat [StringBuilder vs String concatenation w toString() w Javie] (http://stackoverflow.com/questions/1532461/stringbuilder-vs-string-concatenation-in-tostring-in-java) –

Odpowiedz

41

Użycie konkatenacji jest tłumaczone na operacje StringBuilder przez kompilator.

Aby zobaczyć, jak kompilator robi, wezmę klasę próbki, skompiluję ją i zdekompiluję za pomocą jadu, aby zobaczyć, jaki jest wygenerowany kod bajtowy.

Original klasa:

public void method1() { 
    System.out.println("The answer is: " + 42); 
} 

public void method2(int value) { 
    System.out.println("The answer is: " + value); 
} 

public void method3(int value) { 
    String a = "The answer is: " + value; 
    System.out.println(a + " what is the question ?"); 
} 

dekompilowana klasa:

public void method1() 
{ 
    System.out.println("The answer is: 42"); 
} 

public void method2(int value) 
{ 
    System.out.println((new StringBuilder("The answer is: ")).append(value).toString()); 
} 

public void method3(int value) 
{ 
    String a = (new StringBuilder("The answer is: ")).append(value).toString(); 
    System.out.println((new StringBuilder(String.valueOf(a))).append(" what is the question ?").toString()); 
} 
  • Na method1 kompilator przeprowadzić operację w czasie kompilacji.
  • Na method2 łączenie jest równoważne ręcznemu użyciu StringBuilder.
  • Na method3 konkatenacja jest zdecydowanie zła, ponieważ kompilator tworzy drugi obiekt zamiast ponownego użycia poprzedniego.

Moja prosta zasada mówi, że konkatenacje są dobre, chyba że trzeba ponownie połączyć wynik: na przykład w pętlach lub gdy trzeba zapisać wynik pośredni.

+3

W specyfikacji języka Java ZALICZA, że jest to, co zrobiłby kompilator, pozostawiając otwartą możliwość, że nie będzie. 15.18.1.2 Optymalizacja konkatenacji ciągów znaków – avgvstvs

+0

Jakiekolwiek pojęcie, jak długo ta optymalizacja obowiązuje w JVM SUN i czy istnieje w maszynach IBM JVM? – avgvstvs

+0

Myślę, że prawdopodobnie jest tutaj w JVM firmy Sun od wersji 1.4 i nie wiem, czy istnieje w JVM IBM. Możesz sprawdzić za pomocą dekompilatora, takiego jak jad. – gabuzo

20

Twój zespół musi się dowiedzieć o reasons for avoiding repeated string concatenation.

Na pewno czasy, kiedy to ma sens używania StringBuffer - zwłaszcza podczas tworzenia ciąg w pętli, zwłaszcza jeśli nie są pewien że tam będzie kilka iteracji w pętli. Zauważ, że nie chodzi tylko o tworzenie nowych obiektów - jest to kwestia kopiowania wszystkich już dodanych danych tekstowych. Pamiętaj również, że przydzielanie obiektów jest "zasadniczo bezpłatne", jeśli nie uwzględniasz wyrzucania śmieci. Tak, jeśli w obecnej generacji jest wystarczająco dużo miejsca, chodzi o zwiększenie wskaźnika ... ale:

  • Pamięć ta musiała zostać w pewnym momencie wyczyszczona. To nie jest za darmo.
  • Skracasz czas potrzebny do następnego GC. GC nie jest bezpłatny.
  • Jeśli twój obiekt żyje w następnej generacji, może to potrwać dłużej, zanim zostanie oczyszczony - znowu, nie za darmo.

Wszystkie te rzeczy są dostatecznie tanie tym, że jest to „zwykle” nie warto gięcia projekt od elegancji, aby uniknąć tworzenia obiektów ... ale nie należy traktować ich jako wolnego.

Z drugiej strony nie ma sensu korzystanie z StringBuffer w przypadkach, w których nie będą potrzebne pośrednie ciągi. Na przykład:

String x = a + b + c + d; 

jest co najmniej tak samo skuteczny jak:

StringBuffer buffer = new StringBuffer(); 
buffer.append(a); 
buffer.append(b); 
buffer.append(c); 
buffer.append(d); 
String x = buffer.toString(); 
+0

Kompilator zamienia dwa ciągi "Temat" + "Predykat" w – avgvstvs

+0

buf.append ("Subject") .append ("Predicate"). toString ()? – avgvstvs

+0

@ matt.seil: Właściwie, jeśli istnieją ciągi * stałe *, kompilator Java użyje po prostu "Subject Predicate". Ale tak, w innych przypadkach używa on StringBuffer/StringBuilder za kulisami. –

5

dla małych powiązań, można po prostu użyć String i + ze względu na czytelność. Wydajność nie ucierpi. Ale jeśli wykonujesz wiele operacji łączenia, przejdź do StringBuffer.

0

Rozważmy następujący test wydajności, jesteśmy konstruowania dynamiczny ciąg wewnątrz 100000 iteracji pętli:

public void constructDynamicString() 
    { 
     long startTime = System.currentTimeMillis(); 
     String s = "test"; 
     //StringBuffer s = new StringBuffer("test"); 
     //StringBuilder s = new StringBuilder("test"); 
     for(int i=0; i<100000; i++) 
     { 
      s += "concat"; 
      //s.append("concat"); 
     } 

     long endTime = System.currentTimeMillis(); 

     System.out.println("Concat time ====== " + (endTime - startTime)); 
    } 

Prowadzimy powyższe badanie 3 razy, za każdym podejściem naraz, i otrzymujemy następujący wynik :

With String, it tooks: 39068 ms 
With StringBuffer, it tooks: 7ms 
With StringBuilder, it tooks: 4ms 

z powyższych wyników, możemy zauważyć, że za pomocą String obiekt do tworzenia dynamicznych ciąg znaków jest zdecydowanie wolniejsze niż przy użyciu StringBuffer i StringBuilder ze względu na fakt niezmienności.

Dodatkowo StringBuilder jest szybszy niż StringBuffer ponieważ nie dba o synchronizacji (choć wygląda nieco szybciej, prędkość będzie stosunkowo różnią się jeśli zwiększamy liczbę iteracji).

Teraz, w celu podjęcia decyzji, która klasa używać, wziąć pod uwagę następujące czynniki:

  1. Jeśli tworzysz statyczny ciąg znaków, które nie powinny być modyfikowane w całym przebiegu programu, a następnie użyć Obiekt String.
  2. Jeśli tworzysz dynamiczny ciąg znaków, który musi być współużytkowany przez wiele wątków, rozważ skorzystanie z StringBuffer.
  3. Jeśli nie istnieją oba czynniki (co jest bardzo częstym przypadkiem), należy użyć StringBuilder.

Źródło: StringBuilder VS StringBuffer