W jaki sposób łączysz dwa stringi ze sobą?! Czy tak jak w poniższym przykładzie??
public class Konkatenacja { public static void main(String[] args) { String zd="Ala ma kota"; zd+=", a kot ma zabawkę"; System.out.println(zd); // Ala ma kota, a kot ma zabawkę } }
Aby przekonać się jak bardzo jest to nieoptymalny proces musimy pokusić się o pomiar czasu wykonywania tej operacji. Najlepiej zrobić to dla procesu wielokrotnego łączenia stringów, aby czas takiej operacji był wyraźnie rozróżnialny1optymalizacja pojedynczego łączenia stringu w zasadzie nie ma sensu – zmiana podejścia i zysk z tego powodu jest zauważalny w programach, gdzie wielokrotnie dokonujemy konkatenacji. W tym celu wystarczy zmierzyć czas systemowy jaki upłynął od początku do końca tej operacji.
public class KonkatenacjaStringow { public static void main(String[] args) { long time0; String s="Ala ma kota"; time0=System.currentTimeMillis(); for (int i=0; i<10000; i++) s+="Ala ma kota"; System.out.println(System.currentTimeMillis()-time0); } }
Jaki otrzymaliśmy wynik?!
Zależny jest on od szybkości komputera na którym pracujemy, więc niewiele nam mówi, że taka operacja wykonuje się 100 czy 3000ms (3s). Dlatego też chciałbym przedstawić nową klasę StringBuilder która to wprowadza nową (optymalną) jakość w zakresie wielokrotnego łączenia stringów. Poniżej jest porównanie działania obu metod konkatenacji stringów. Dla zwiększenia efektu – stringi będziemy łączyć 100000 razy zamiast 10000 jak w poprzednim przykładzie.
public class KonkatenacjaStringow { public static void main(String[] args) { StringBuilder sb=new StringBuilder(); sb.insert(0, "Ala ma kota"); long time0; time0=System.currentTimeMillis(); // <-- stoper start for (int i=0; i<100000; i++) sb.append("Ala ma kota"); System.out.println(System.currentTimeMillis()-time0); // <-- stoper stop System.out.println("Długość zbudowanego stringa to: "+sb.length()); // <-- dla pewności sprawdzamy długość, czy aby są równe String s="Ala ma kota"; time0=System.currentTimeMillis(); for (int i=0; i<100000; i++) s+="Ala ma kota"; System.out.println(System.currentTimeMillis()-time0); System.out.println("Długość zbudowanego stringa to: "+s.length()); // <-- dla pewności sprawdzamy długość, czy aby są równe } }
Wszystko teraz jest widoczne… za pomocą zwykłego dodawania nasz string budował się 100s(!!!)… a za pomocą StringBuildera… 56ms, czyli blisko 2000 razy szybciej…
Inną klasą realizującą podobną funkcjonalność jest StringBuffer, można się nią posługiwać analogicznie do StringBuildera, co widać na poniższym listingu:
StringBuffer sb=new StringBuffer(); sb.insert(0, "Ala ma kota"); time0=System.currentTimeMillis(); for (int i=0; i<1000000; i++) sb.append("Ala ma kota"); System.out.println(System.currentTimeMillis()-time0); System.out.println("Długość zbudowanego stringa to: "+sb.length());
To co je różni, możemy omówić na poziomie po przypomnieniu tego jak skonstruowany jest String. Łańcuch znakowy typu String jest wewnętrznie zorganizowany jako ciąg znakowy, ale nie możemy wprost odwoływać się do niego jako tablicy, choć z tablicą znaków ma dużo wspólnego – mianowicie jego długość jest… niezmienna. Powoduje to fakt, że próba konkatenacji dwu zmiennych typu String kończy się koniecznością zarezerwowania ciągłej pamięci dla nowej struktury o łącznej długości wynikającej z długości składowych stringów i przekopiowaniem zawartości tych stringów do nowej struktury, przypisaniu nowego adresu (już tej nowej struktury) i zwolnieniu pamięci zajętej przez starą (już niepotrzebną i za krótką) strukturę. StringBuilder i StringBuffer nie tworzą w pamięci jednorodnej struktury, a raczej listę powiązanych ze sobą mniejszych struktur (kawałków ciągów tekstowych). Taka architektura powoduje, że program nie traci czasu na żmudne przekopiowywanie zawartości2kopiowanie jest jedną z bardziej czasochłonnych operacji, dlatego też optymalizacja polega na unikaniu takiej konieczności jeśli tylko się tak da – stąd też mamy np. przekazywanie tablicy przez parametr funkcji gdzie przekazuje się wskaźnik na tablicę, a nie powoduje tworzenia tymczasowej zmiennej będącej kopią całej tablicy z małych do większych struktur w miarę konkatenacji, a jedynie linkuje, czyli dołącza na końcu jednej struktury wskazania (wskaźniki) na następną. StringBuilder od StringBuffer różnią się sposobem przechowywania ciągów znakowych 3łączonych stringów. StringBuilder traktuje te ciągi jako niemodyfikowalne struktury, a StringBuffer jako łańcuch swobodnie powiązanych znaków (char). Implikacją tego jest fakt, że w przypadku potrzeby dodawania w środku łączonych stringów nowych wartości (ciągów znakowych) lepiej jest zastosować klasę StringBuffer4dla tego celu lepiej użyć właśnie tej klasy, bo takie operacje na stringach będą się w niej wykonywać optymalnie szybciej niż w StringBuilder, a w przypadku konkatenacji stringów, które wewnętrznie raczej nie będą już modyfikowane – klasę StringBuilder5Będzie się wykonywała “ciut” szybciej, niż w tym samym celu użyta klasa StringBuffer. Dodatkowe porównanie obu klas przedstawione jest w poniższej tabeli:
StringBuffer Class | StringBuilder Class |
---|---|
StringBuffer is present in Java. | StringBuilder został wprowadzony w Java 5. |
StringBuffer jest synchroniczny. To znaczy, że różne wątki nie mogą jednocześnie wywoływać tej metody. | StringBuilder jest asynchroniczny. To znaczy, że w różnych wątkach można wywoływać tę metodę niezależnie i równolegle. |
Z powodu synchroniczności klasa StringBuffer jest tzw. thread safe class (wielowątkowo bezpieczną). | Z powodu asynchroniczności, klasa ta jest not a thread safe class (wielowątkowo niebezpieczną). |
Z powodu konieczności utrzymania synchroniczności, StringBuffer jest wolniejsza niż StringBuilder. | Z powodu braku konieczności utrzymywania synchroniczności i weryfikowania uruchomień metody w innych wątkach StringBuilder jest dużo szybszą metodą. |