Java HR: StringBuffer vs StringBuilder

Java HR: StringBuffer vs StringBuilder

String jest co prawda wspaniałą i jedną z najważniejszych klas w Javie, jednak nie ma rzeczy idealnych i tutaj z pomocą przychodzą klasy StringBuffer oraz StringBuilder.

Co to jest?


Czym tak właściwie są klasy StringBufferStringBuilder i czemu nie wystarczy String?
Są to klasy reprezentujące sekwencję znaków i w przeciwieństwie do Stringa, który jest klasą niezmienną – immutable (więcej w artykule: Java HR: obiekty niezmienne (immutable)),
ich obiekty są zmienne, tzn. mogą modyfikować swój wewnętrzny stan w tym samym miejscu w pamięci.

Obie te klasy działają wewnętrznie na specjalnym buforze.
Fakt, że String jest niezmienny niesie za sobą szereg korzyści, dlatego jest tak wyjątkową klasą w Javie.
Przykładowo, dzięki temu idealnie nadaje się do bycia kluczem w Hashmapie.
Niestety, jest to obarczone też pewnymi wadami – zwłaszcza jeśli chodzi o wykonywanie operacji na Stringach.
Wszystko jest oczywiście zależne od naszej aplikacji – czasami takiego przetwarzania nie używa się wiele, jednak w niektórych przypadkach może to być krytyczne dla jej wydajności.

Przy bezpośredniej operacji na Stringach za każdym razem zostanie stworzony w pamięci nowy obiekt – nawet przy użyciu takich niepozornych metod jak trim().
Klasycznym przykładem takiej operacji jest konkatenacja (przy pomocy operatora +, który jest przeładowany dla Stringów), częstokroć dokonywana w pętli, wtedy różnice w wydajności są tym bardziej odczuwalne. Tworzone jest wtedy wiele nowych obiektów, które od razu zostają porzucane, zaśmiecając pamięć i dokładając pracy Garbage Collectorowi.
Używając metody append() klasy StringBuffer lub StringBuilder unikamy tego problemu.

Ciekawostka: w aktualnych wersjach Javy do bezpośredniej konkatenacji Stringów kompilator i tak wykorzystuje StringBuilder, za każdym razem tworząc tymczasowo jego obiekt, co również nie pozostaje bez wpływu na wydajność.

Jeszcze raz, najważniejsze różnice:
  • String jest immutable, StringBuilderStringBuffermutable (bufor)
  • String przechowywany jest w String Pool, StringBuilderStringBuffer – w normalnej stercie
  • StringBuilderStringBuffer są znacznie wydajniejsze do celów modyfikacji sekwencji znaków

StringBuilder vs StringBuffer


  • StringBuffer powstał jako pierwszy – w Javie 1.0.
  • StringBuilder został dodany w Javie 1.5 (Java 5) jako jego bezpośredni zamiennik (drop-in replacement).

Oznacza to, że jest w pełni kompatybilny z jego API – dostarcza tych samych metod, więc w miejscu można dokonać podmiany klasy.
Dlaczego?

  • StringBuffer był niewydajny w przypadku większości jego zastosowań. Został zaimplementowany jako klasa thread-safe, wszystkie jego metody są z osobna synchronizowane.
  • StringBuilder to jego nowsza i obecnie zalecana wersja – nie jest synchronizowany, lecz w przypadku aplikacji jednowątkowych nie ma to żadnego znaczenia.

Co prawda różnice w wydajności są niewielkie przy małej liczbie operacji (w przeciwieństwie do porównania ze Stringiem, gdzie różnica w wydajności jest kolosalna), jednak nie ma jakiegokolwiek argumentu za użyciem StringBuffera, więc zawsze lepiej zastąpić go StringBuilderem.

StringBuffer obarczony jest dodatkowymi, niepotrzebnymi kosztami blokowania dostępu.
Jeśli potrzebna jest synchronizacja wątków – lepiej jest użyć klasy StringBuilder i zwyczajnie umieścić sekwencję operacji w bloku synchronized {}. StringBuffer jest natomiast synchronizowany na poziomie pojedynczych operacji, co rzadko kiedy się przydaje.

Jak widać, nie bez powodu powstał jego zamiennik.

Podobnie potraktowane zostały:
  • Vector -> ArrayList
  • Hashtable -> HashMap

Użycie w praktyce


Wzajemna relacja
Klasa String posiada konstruktor przyjmujący jako parametr StringBuffer lub StringBuilder.
Można z nich też uzyskać Stringa przy pomocy metody toString(). Można też użyć obiektu którejś z tych dwóch klas bezpośrednio na wyjściu, np. w System.out.println().

Wszystkie 3 klasy rozszerzają CharSequence, ale nie jest możliwe wzajemne rzutowanie (poprzez cast).

Przykłady

String:

StringBuffer:

StringBuilder:

Istotna różnica w użyciu – jeśli przekażemy String do metody i wewnątrz niej zostanie on w jakikolwiek sposób „zmodyfikowany” (pozornie), na zewnątrz oryginalny obiekt pozostanie taki sam.
Jeśli zrobimy to samo ze StringBuilderem lub StringBufferem, wartość obiektu na zewnątrz (poza metodą) również ulegnie zmianie.
[przykład]

Podsumowując:
  • czasami wystarczy tylko String (gdy nie próbujemy zmieniać jego wartości)
  • do manipulacji łańcuchami znaków potrzebny jest StringBuilder
  • w aplikacjach wielowątkowych przydatny jest StringBuffer (chociaż zalecam użycie StringBuildera + jawna synchronizacja)

Ciekawostka: lock elision – niektóre wersje JVM począwszy od Java 6 wprowadziły mechanizm, w którym kompilator może wykryć czy synchronizacja danego kodu jest potrzebna, czy może inne wątki nie będą miały nigdy do niego dostępu.
Ta optymalizacja potrafi wpłynąć na wydajność StringBuffera.

Więcej w tym temacie: https://www.infoq.com/articles/java-threading-optimizations-p1

Informatyk, programista. Obecnie Java Developer (Web Fullstack), właściciel studia Berrygames oraz prezes koła TK Games na Politechnice Wrocławskiej.

Wszystkie artykuły autora>>

1 komentarz do “Java HR: StringBuffer vs StringBuilder”:

  1. Hello there,

    My name is Aly and I would like to know if you would have any interest to have your website here at sjezierski.pl promoted as a resource on our blog alychidesign.com ?

    We are in the midst of updating our broken link resources to include current and up to date resources for our readers. Our resource links are manually approved allowing us to mark a link as a do-follow link as well
    .
    If you may be interested please in being included as a resource on our blog, please let me know.

    Thanks,
    Aly

Dodaj komentarz