String, StringBuffer i StringBuilder

213

Proszę powiedzieć mi rzeczywistą sytuację czasu, aby porównać String, StringBufferi StringBuilder?

JavaUser
źródło

Odpowiedzi:

378

Różnica zmienności:

Stringjest niezmienna , jeśli starają się zmieniać swoje wartości, inny obiekt zostanie utworzony, natomiast StringBufferi StringBuilderzmienne , aby mogli zmienić ich wartości.

Różnica w zabezpieczeniu gwintu:

Różnica między StringBufferi StringBuilderjest StringBufferbezpieczna dla wątków. Więc jeśli aplikacja musi być uruchomiona tylko w jednym wątku, lepiej jest użyć StringBuilder. StringBuilderjest bardziej wydajny niż StringBuffer.

Sytuacje:

  • Jeśli łańcuch nie ma się zmienić, użyj klasy String, ponieważ Stringobiekt jest niezmienny.
  • Jeśli łańcuch może się zmienić (na przykład: dużo logiki i operacji w konstrukcji łańcucha) i będzie można uzyskać do niego dostęp tylko z jednego wątku, użycie parametru StringBuilderjest wystarczające.
  • Jeśli ciąg może się zmienić i będzie dostępny z wielu wątków, użyj parametru StringBufferponieważ StringBufferjest synchroniczne, aby zapewnić bezpieczeństwo wątków.
bakkal
źródło
16
Ponadto używanie ciągu znaków do operacji logicznych jest raczej powolne i wcale nie jest zalecane, ponieważ JVM konwertuje ciąg znaków na bufor ciągu w kodzie bajtowym. Wiele kosztów ogólnych jest marnowanych na konwersję z String do StringBuffer, a następnie z powrotem do String.
Pieter van Niekerk
2
Więc Stringskiedy zmienić wartość tworzona jest kolejnym celem. Czy stare odwołanie do obiektu jest unieważniane, tak aby mogło być śmieciami gromadzonymi przez, GCczy może nawet śmieciami?
Harsh Vardhan
@PietervanNiekerk Co masz na myśli przez operacje logiczne?
emlai
rozumiem, że operacje logiczne są podstawowymi ciągami. Teraz chciałbym zapytać, jak stwierdził @Peter, czy powinniśmy zacząć używać StringBuffer zamiast ciągów we wszystkich przypadkach, czy są jakieś szczególne przypadki?
JavaDragon,
@bakkal Czy mogę używać wszystkich metod Stringz StringBuilder?
roottraveller,
47
  • Używasz, Stringgdy niezmienna struktura jest odpowiednia; uzyskanie nowej sekwencji znaków z a Stringmoże pociągać za sobą niedopuszczalne obniżenie wydajności, zarówno w czasie pracy procesora, jak i pamięci (uzyskiwanie podciągów jest wydajne procesorowo, ponieważ dane nie są kopiowane, ale oznacza to, że potencjalnie znacznie większa ilość danych może pozostać przydzielona).
  • Używasz go, StringBuildergdy chcesz utworzyć zmienną sekwencję znaków, zwykle do połączenia kilku sekwencji znaków razem.
  • Używasz StringBufferw tych samych okolicznościach, których używałbyś StringBuilder, ale gdy zmiany w łańcuchu bazowym muszą być zsynchronizowane (ponieważ kilka wątków odczytuje / modyfikuje bufor łańcucha).

Zobacz przykład tutaj .

Artefakt
źródło
2
zwięzłe, ale niekompletne, brakuje w nim podstawowego powodu używania StringBuilder / Buffer, a mianowicie zmniejszenia lub wyeliminowania ponownego przydzielania i kopiowania tablicy regularnego zachowania konkatenacji String.
1
„Używasz Stringa, gdy masz do czynienia z niezmiennymi łańcuchami” - nie ma sensu. Instancje String SĄ niezmienne, więc może komentarz powinien brzmieć „Użyj String, gdy użycie pamięci z powodu niezmienności nie ma znaczenia”. Przyjęta odpowiedź obejmuje całkiem nieźle podstawy.
fooMonster,
28

Podstawy:

Stringto niezmienna klasa, której nie można zmienić. StringBuilderto zmienna klasa, do której można dołączać, zamieniać lub usuwać znaki i ostatecznie przekonwertować na a String StringBufferjest oryginalną zsynchronizowaną wersjąStringBuilder

Powinieneś preferować StringBuilderwe wszystkich przypadkach, w których tylko jeden wątek uzyskuje dostęp do obiektu.

Szczegóły:

Zauważ też, że StringBuilder/Buffersto nie magia, po prostu używają Array jako obiektu wspierającego i że Array musi zostać ponownie przydzielony, kiedy tylko się zapełni. Upewnij się i stwórz swoje StringBuilder/Bufferobiekty wystarczająco duże, aby nie musiały być ciągle zmieniane przy każdym .append()wywołaniu.

Zmiana rozmiaru może stać się bardzo zdegenerowana. Zasadniczo zmienia rozmiar macierzy podkładu na 2-krotność jego obecnej wielkości za każdym razem, gdy trzeba ją rozszerzyć. Może to spowodować przydzielenie dużej ilości pamięci RAM i niewykorzystanie jej, gdy StringBuilder/Bufferklasy zaczną się powiększać.

W Javie String x = "A" + "B";stosuje się StringBuilderza kulisami. W prostych przypadkach nie ma korzyści z deklarowania własnego. Ale jeśli Stringbudujesz duże obiekty, powiedzmy mniej niż 4k, to deklarowanie StringBuilder sb = StringBuilder(4096);jest znacznie wydajniejsze niż konkatenacja lub użycie domyślnego konstruktora, który ma tylko 16 znaków. Jeśli masz Stringmniej niż 10k, zainicjuj go konstruktorem na 10k, aby być bezpiecznym. Ale jeśli zostanie zainicjowany do 10k, to napiszesz 1 znak więcej niż 10k, zostanie on ponownie przydzielony i skopiowany do tablicy 20k. Tak więc inicjowanie wysokiego jest lepsze niż niskie.

W przypadku automatycznej zmiany rozmiaru przy 17. znaku macierz zastępcza zostaje ponownie przydzielona i skopiowana do 32 znaków, przy 33 znaku to się dzieje ponownie i można ponownie przydzielić i skopiować tablicę na 64 znaki. Możesz zobaczyć, jak to się degeneruje do wielu ponownych alokacji i kopii, których tak naprawdę starasz się unikać StringBuilder/Buffer.

Pochodzi z kodu źródłowego JDK 6 dla AbstractStringBuilder

   void expandCapacity(int minimumCapacity) {
    int newCapacity = (value.length + 1) * 2;
        if (newCapacity < 0) {
            newCapacity = Integer.MAX_VALUE;
        } else if (minimumCapacity > newCapacity) {
        newCapacity = minimumCapacity;
    }
        value = Arrays.copyOf(value, newCapacity);
    }

Najlepszą praktyką jest zainicjowanie StringBuilder/Buffernieco większego, niż myślisz, że będziesz potrzebować, jeśli nie wiesz od razu, jak duży Stringbędzie, ale możesz się domyślić. Jeden przydział nieco więcej pamięci niż potrzebujesz będzie lepszy niż wiele ponownych przydziałów i kopii.

Uważaj również na zainicjowanie znaku StringBuilder/Buffera, Stringponieważ przydzieli tylko rozmiar ciągu + 16 znaków, co w większości przypadków po prostu rozpocznie zdegenerowaną ponowną alokację i cykl kopiowania, którego próbujesz uniknąć. Poniższy kod pochodzi bezpośrednio z kodu źródłowego Java 6.

public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
    }

Jeśli przypadkiem trafisz na instancję StringBuilder/Buffer, której nie utworzyłeś i nie możesz kontrolować wywoływanego konstruktora, istnieje sposób na uniknięcie zdegenerowanego ponownego przydzielania i kopiowania. Zadzwoń .ensureCapacity()z rozmiarem, w którym chcesz się Stringdopasować.

Alternatywy:

Dla przypomnienia, jeśli robisz naprawdę ciężkie String budowanie i manipulowanie, istnieje o wiele bardziej zorientowana na wydajność alternatywa zwana Liny .

Inną alternatywą jest utworzenie StringListimplementacji poprzez podklasę ArrayList<String>i dodanie liczników do śledzenia liczby znaków na każdej .append()i wszystkich operacjach mutacji na liście, a następnie przesłonięcie, .toString()aby utworzyć StringBuilderdokładnie taki rozmiar, jakiego potrzebujesz i zapętlić listę i budować wyjściowy, możesz nawet uczynić tę StringBuilderinstancję zmienną i „buforować” wyniki .toString()i tylko ponownie ją wygenerować, gdy coś się zmieni.

Nie zapominaj również o String.format()budowaniu stałych sformatowanych danych wyjściowych, które mogą być zoptymalizowane przez kompilator, ponieważ są lepsze.

użytkowników177800
źródło
1
Czy String x = "A" + "B";naprawdę kompiluje się, aby być StringBuilder? Dlaczego nie miałby po prostu się skompilować String x = "AB";, powinien używać StringBuilder tylko wtedy, gdy składniki nie są znane w czasie kompilacji.
Matt Greer
może zoptymalizować stałe String, nie pamiętam z ostatniej dekompilacji kodu bajtu, ale wiem, że jeśli są tam jakieś zmienne, na pewno użyje implementacji StringBuilder. Możesz pobrać kod źródłowy JDK i przekonać się sam. „A” + „B” jest z pewnością wymyślonym przykładem.
Zastanawiałem się nad String.format (). Nigdy tak naprawdę nie widziałem, żeby był wykorzystywany w projektach. Zwykle jest to StringBuilder. Cóż, zwykle tak naprawdę jest to „A” + „B” + „C”, ponieważ ludzie są leniwi;) Zawsze miałem tendencję do korzystania z StringBuilder, nawet jeśli byłyby to tylko dwa łańcuchy łączone, ponieważ w przyszłości być może dodanych będzie więcej łańcuchów . Nigdy nie użyłem String.format () głównie dlatego, że nigdy nie pamiętałem, jaki JDK został wprowadzony - widzę, że to JDK1.5, użyłbym go na rzecz innych opcji.
jamiebarrow
9

Masz na myśli konkatenację?

Przykład ze świata rzeczywistego: chcesz utworzyć nowy ciąg z wielu innych .

Na przykład, aby wysłać wiadomość:

Strunowy

String s = "Dear " + user.name + "<br>" + 
" I saw your profile and got interested in you.<br>" +
" I'm  " + user.age + "yrs. old too"

StringBuilder

String s = new StringBuilder().append.("Dear ").append( user.name ).append( "<br>" ) 
          .append(" I saw your profile and got interested in you.<br>") 
          .append(" I'm  " ).append( user.age ).append( "yrs. old too")
          .toString()

Lub

String s = new StringBuilder(100).appe..... etc. ...
// The difference is a size of 100 will be allocated upfront as  fuzzy lollipop points out.

StringBuffer (składnia jest dokładnie taka sama jak w StringBuilder, efekty różnią się)

O

StringBuffer vs. StringBuilder

Pierwsza jest zsynchronizowana, a później nie.

Tak więc, jeśli wywołasz ją kilka razy w jednym wątku (co stanowi 90% przypadków), StringBuilderbędzie działać znacznie szybciej, ponieważ nie przestanie sprawdzać, czy jest właścicielem blokady wątku.

Dlatego zaleca się stosowanie StringBuilder(chyba że masz więcej niż jeden wątek uzyskujący dostęp do niego w tym samym czasie, co jest rzadkie)

Stringkonkatenacja ( przy użyciu operatora + ) może być zoptymalizowana przez kompilator do użycia StringBuilderpod spodem, więc nie trzeba się już martwić, w dawnych czasach Java było to coś, o czym wszyscy mówią, że należy unikać za wszelką cenę, ponieważ każdą konkatenację utworzył nowy obiekt String. Współczesne kompilatory już tego nie robią, ale nadal dobrą praktyką jest używanie ich StringBuilderna wypadek, gdybyś używał „starego” kompilatora.

edytować

Dla kogo ciekawi, to właśnie robi kompilator dla tej klasy:

class StringConcatenation {
    int x;
    String literal = "Value is" + x;
    String builder = new StringBuilder().append("Value is").append(x).toString();
}

javap -c StringConcatenation

Compiled from "StringConcatenation.java"
class StringConcatenation extends java.lang.Object{
int x;

java.lang.String literal;

java.lang.String builder;

StringConcatenation();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   new #2; //class java/lang/StringBuilder
   8:   dup
   9:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   12:  ldc #4; //String Value is
   14:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   17:  aload_0
   18:  getfield    #6; //Field x:I
   21:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   24:  invokevirtual   #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   27:  putfield    #9; //Field literal:Ljava/lang/String;
   30:  aload_0
   31:  new #2; //class java/lang/StringBuilder
   34:  dup
   35:  invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
   38:  ldc #4; //String Value is
   40:  invokevirtual   #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   43:  aload_0
   44:  getfield    #6; //Field x:I
   47:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   50:  invokevirtual   #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   53:  putfield    #10; //Field builder:Ljava/lang/String;
   56:  return

}

Linie ponumerowane 5–27 dotyczą ciągu o nazwie „dosłowny”

Linie o numerach 31–53 dotyczą ciągu o nazwie „konstruktor”

Nie ma różnicy, dokładnie ten sam kod jest wykonywany dla obu łańcuchów.

OscarRyz
źródło
2
to naprawdę zły przykład. Zainicjowanie StringBuilder słowem „Drogi” oznacza, że ​​pierwszy .append () spowoduje realokację i skopiowanie. Całkowicie negując jakąkolwiek wydajność, którą próbujesz uzyskać w porównaniu do „normalnej” konkatenacji. Lepszym przykładem byłoby utworzenie go z początkowym rozmiarem, który pomieści całą zawartość końcowego ciągu.
1
Zasadniczo nie jest dobrą praktyką stosowanie StringBuilderkonkatenacji ciągów po prawej stronie zadania. Każda dobra implementacja użyje StringBuilder za kulisami, jak mówisz. Co więcej, twój przykład "a" + "b"zostałby skompilowany w jeden literał, "ab"ale jeśli go użyjesz StringBuilder, spowoduje to dwa niepotrzebne wywołania append().
Mark Peters
@ Mark Nie chciałem używać, "a"+"b"ale żeby powiedzieć, co było połączeniem String , zmieniam to, aby było jawne. Nie mówisz, dlaczego nie jest dobrą praktyką. Właśnie to robi (nowoczesny) kompilator. @fuzzy, zgadzam się, zwłaszcza gdy wiesz, jaki byłby końcowy rozmiar łańcucha (w przybliżeniu).
OscarRyz
1
Nie wydaje mi się, aby była to szczególnie zła praktyka, ale na pewno nie chciałbym, aby jakieś zalecenia były podobne. Jest niezgrabny, trudny do odczytania i nadmiernie gadatliwy. Ponadto zachęca do błędów takich jak twoje, w których rozpadasz dwa literały, które inaczej można by skompilować jako jedno. Tylko jeśli profilowanie powiedziałoby mi, że to robi różnicę, zrobiłbym to po swojemu.
Mark Peters
@Mark Mam to. Myślałem bardziej w „dużej części kodu jak szablon”, a nie w każdym regularnym dosłownym ciągu znaków. Ale tak, zgadzam się, ponieważ robią to samo w dzisiejszych czasach, to nie ma sensu (10 lat temu było powodem do odrzucenia zmiany w wersji kodu) :)
OscarRyz
8
-------------------------------------------------- --------------------------------
                  String StringBuffer StringBuilder
-------------------------------------------------- --------------------------------                 
Miejsce przechowywania | Kupa sterty puli ciągów strunowych
Modyfikowalne | Nie (niezmienne) Tak (zmienne) Tak (zmienne)
Bezpieczny wątek | Tak Tak Nie
 Wydajność | Szybko Bardzo wolno Szybko
-------------------------------------------------- --------------------------------
Abhijit Maity
źródło
Dlaczego wydajność String jest szybka, a StringBuffer bardzo mała?
gaurav
1
@gaurav możesz odczytać jego kod źródłowy, wszystkie metody w StringBuffer są synchronisedi dlatego .
Hearen
8

Rodzina strun

Strunowy

String classReprezentuje ciągi znaków. Wszystkie literały łańcuchowe w programie Java, takie jak "abc"zaimplementowane jako instancje tej klasy.

Obiekty łańcuchowe są niezmienne po ich utworzeniu, którego nie możemy zmienić. ( Ciągi są stałymi )

  • Jeśli ciąg jest tworzony za pomocą konstruktora lub metody to te ciągi będą przechowywane w pamięci sterty , jak również SringConstantPool. Ale przed zapisaniem w puli wywołuje intern()metodę sprawdzania dostępności obiektu o tej samej zawartości w puli przy użyciu metody równości. Jeśli kopia ciągów jest dostępna w puli, zwraca odwołanie. W przeciwnym razie obiekt String zostanie dodany do puli i zwróci odwołanie.

    • Język Java zapewnia specjalną obsługę operatora konkatenacji ciągów ( +) oraz konwersji innych obiektów na ciągi. Łączenie ciągów jest realizowane za pośrednictwem klasy StringBuilder (lub StringBuffer) i jej metody dołączania.

    String heapSCP = new String("Yash");
    heapSCP.concat(".");
    heapSCP = heapSCP + "M";
    heapSCP = heapSCP + 777;
    
    // For Example: String Source Code 
    public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
  • Literały łańcuchowe są przechowywane w StringConstantPool.

    String onlyPool = "Yash";

StringBuilder i StringBuffer to zmienna sekwencja znaków. Oznacza to, że można zmienić wartość tych obiektów. StringBuffer ma te same metody co StringBuilder, ale każda metoda w StringBuffer jest zsynchronizowana, więc jest bezpieczna dla wątków.

  • Dane StringBuffer i StringBuilder można tworzyć tylko przy użyciu nowego operatora. Są więc przechowywane w pamięci sterty.

  • Instancje StringBuilder nie są bezpieczne dla wielu wątków. Jeśli taka synchronizacja jest wymagana, zaleca się użycie StringBuffer.

    StringBuffer threadSafe = new StringBuffer("Yash");
    threadSafe.append(".M");
    threadSafe.toString();
    
    StringBuilder nonSync = new StringBuilder("Yash");
    nonSync.append(".M");
    nonSync.toString();
  • StringBuffer i StringBuilder wywierają metod specjalnych, takich jak., replace(int start, int end, String str)I reverse().

    UWAGA : StringBuffer i SringBuilder są mutowalne, ponieważ zapewniają implementację Appendable Interface.


Kiedy stosować który.

  • Jeśli nie zamierzasz zmieniać wartości za każdym razem, lepiej użyć String Class. W ramach Generics, jeśli chcesz posortować Comparable<T>lub porównać wartości, przejdź do String Class.

    //ClassCastException: java.lang.StringBuffer cannot be cast to java.lang.Comparable
    Set<StringBuffer> set = new TreeSet<StringBuffer>();
    set.add( threadSafe );
    System.out.println("Set : "+ set);
  • Jeśli zamierzasz modyfikować wartość za każdym razem, gdy korzystasz z StringBuilder, który jest szybszy niż StringBuffer. Jeśli wiele wątków modyfikuje wartość, przejdź do StringBuffer.

Yash
źródło
4

Ponadto StringBufferjest bezpieczny dla wątków, co StringBuildernie jest.

Tak więc w sytuacji czasu rzeczywistego, gdy różne wątki uzyskują do niego dostęp, StringBuildermoże mieć nieokreślony skutek.

Lars Andren
źródło
3

Pamiętaj, że jeśli używasz Java 5 lub nowszej, powinieneś użyć StringBuilderzamiast StringBuffer. Z dokumentacji API:

Jako uwalniania JDK 5, do tej klasy zostały uzupełnione o podobnym poziomie zaprojektowane do użytku przez jedną gwintu StringBuilder. StringBuilderKlasa powinna być powszechnie stosowane w preferencji do tego, ponieważ obsługuje wszystkie te same operacje, ale jest szybsze, gdyż nie wykonuje żadnej synchronizacji.

W praktyce prawie nigdy nie użyjesz tego z wielu wątków jednocześnie, więc synchronizacja, StringBufferktóra to robi, prawie zawsze jest niepotrzebnym narzutem.

Jesper
źródło
3

Osobiście nie sądzę, aby można było z tego skorzystać w prawdziwym świecie StringBuffer. Kiedy miałbym kiedykolwiek chcieć komunikować się między wieloma wątkami, manipulując sekwencją znaków? To wcale nie brzmi pożytecznie, ale może jeszcze nie widzę światła :)

fredoverflow
źródło
3

Różnica między String a pozostałymi dwiema klasami polega na tym, że String jest niezmienny, a pozostałe dwie klasy są zmiennymi.

Ale dlaczego mamy dwie klasy do tego samego celu?

Powodem jest to, że StringBufferwątek jest bezpieczny i StringBuildernie jest. StringBuilderjest nową klasą StringBuffer Apii został wprowadzony JDK5i jest zawsze zalecany, jeśli pracujesz w środowisku jednowątkowym, ponieważ jest go dużoFaster

Aby uzyskać szczegółowe informacje, przeczytaj http://www.codingeek.com/java/stringbuilder-and-stringbuffer-a-way-to-create-mutable-strings-in-java/

Hitesh Garg
źródło
2

W języku Java ciąg jest niezmienny. Będąc niezmiennymi, mamy na myśli, że po utworzeniu Łańcucha nie możemy zmienić jego wartości. StringBuffer można modyfikować. Po utworzeniu obiektu StringBuffer po prostu dołączamy treść do wartości obiektu, zamiast tworzyć nowy obiekt. StringBuilder jest podobny do StringBuffer, ale nie jest bezpieczny dla wątków. Metody StingBuilder nie są zsynchronizowane, ale w porównaniu do innych Ciągów, Konstruktor Ciągów działa najszybciej. Różnicę między String, StringBuilder i StringBuffer można poznać , implementując je.

rajat ghai
źródło