Czyszczenie bufora łańcuchowego / konstruktora po pętli


122

Jak wyczyścić bufor ciągów w Javie po pętli, aby w następnej iteracji był używany czysty bufor ciągów?


2
Kolejne pytanie: dlaczego używasz SB tylko do jednej iteracji pętli? Czy jest inna wewnętrzna iteracja? SB nie jest warta, jeśli chcesz po prostu zrobić A + B + C + D (kompilator Java wewnętrznie użyje SB). Pomaga, jeśli chcesz warunkowo dodać części ciągów, ale poza tym ... po prostu użyj „+”.
helios

Odpowiedzi:


135

Jedną z opcji jest użycie metody usuwania w następujący sposób:

StringBuffer sb = new StringBuffer();
for (int n = 0; n < 10; n++) {
   sb.append("a");

   // This will clear the buffer
   sb.delete(0, sb.length());
}

Inna opcja (oczyszczanie bitów) wykorzystuje setLength (int len) :

sb.setLength(0);

Zobacz Javadoc, aby uzyskać więcej informacji:


15
Coś mniej bzdurnego byłoby po prostu zadeklarowanie StringBuffer wewnątrz pętli.
Mark Elliot,

11
Ach, myślę, że sb.setLength (0); jest czystszy i wydajniejszy niż deklarowanie go wewnątrz pętli. Twoje rozwiązanie jest sprzeczne z korzyścią dla wydajności wynikającą z używania StringBuffer ...
Jon,

6
Myślę, że korzyść z wydajności wynika ze zmienności łańcucha, a nie z zapisania instancji. oto szybki test iteracji 1e8: pętla wewnętrzna (2,97 s): ideone.com/uyyTL14w , pętla zewnętrzna (2,87 s): ideone.com/F9lgsIxh
Mark Elliot

6
A propos wydajności: O ile kod nie jest dostępny w scenariuszu wielowątkowym, należy używać StringBuilder zamiast StringBuffer - zobacz javadoc: „Tam, gdzie jest to możliwe, zaleca się, aby ta klasa była używana zamiast StringBuffer, ponieważ będzie ona szybsza pod większość wdrożeń ”.
Rahel Lüthy

4
Jedyną zaletą tworzenia SB na zewnątrz jest brak utraty jego wewnętrznego (potencjalnie długiego) znaku []. Jeśli w pierwszym iteratorze wystarczająco urósł, druga pętla nie będzie musiała zmieniać rozmiaru znaku []. Jednak aby uzyskać przewagę, „metoda czyszczenia” będzie musiała zachować rozmiar wewnętrznej tablicy. setLength robi to, ale ustawia także \ u0000 wszystkie nieużywane znaki w SB, więc jest mniej wydajne niż po prostu tworzenie nowego SB z dobrą początkową pojemnością. Deklarowanie wewnątrz pętli jest lepsze.
helios

48

Najłatwiejszym sposobem ponownego użycia StringBufferjest użycie metodysetLength()

public void setLength(int newLength)

Możesz mieć taki przypadek

StringBuffer sb = new StringBuffer("HelloWorld");
// after many iterations and manipulations
sb.setLength(0);
// reuse sb

2
@MMahmoud, w przykładowym kodzie powinien czytać setLengthzamiast setlength.
OnaBai

Kiedy widzę źródło kodu setLength, jak to działa ( gist.github.com/ebuildy/e91ac6af2ff8a6b1821d18abf2f8c9e1 ) tutaj liczba będzie zawsze większa niż newLength (0)?
Thomas Decaux

20

Masz dwie możliwości:

Albo użyj:

sb.setLength(0);  // It will just discard the previous data, which will be garbage collected later.  

Albo użyj:

sb.delete(0, sb.length());  // A bit slower as it is used to delete sub sequence.  

UWAGA

Unikaj deklarowania StringBufferlub StringBuilderobiektów w pętli, w przeciwnym razie utworzy nowe obiekty z każdą iteracją. Tworzenie obiektów wymaga zasobów systemowych, miejsca, a także czasu. Dlatego na dłuższą metę unikaj deklarowania ich w pętli, jeśli to możliwe.



5

Proponuję stworzyć nowy StringBuffer(lub nawet lepszy StringBuilder) dla każdej iteracji. Różnica w wydajności jest naprawdę nieistotna, ale Twój kod będzie krótszy i prostszy.


2
Jeśli masz jakiekolwiek dowody na taką różnicę w wydajności, udostępnij je. (Z przyjemnością zobaczę również statystyki dotyczące miejsc, w których Java jest najczęściej używana i według której definicji „głównie”.)
Eli Acherkan,

1
Powszechnie wiadomo, że alokacja (nowe słowo kluczowe w Javie) jest droższa niż zwykła zmiana niektórych pól tego samego obiektu. W przypadku „większości”, które można by zastąpić, jest ponad 1,5 miliarda urządzeń z Androidem, wszystkie wykorzystujące Javę.
Louis CAD

1
Zgadzam się, że w niektórych przypadkach czytelność kodu jest ważniejsza niż wydajność, ale w tym przypadku jest to tylko jedna linia kodu! Możesz też uruchomić test porównawczy, który udowodni, że alokacja i tworzenie obiektów oraz zbieranie śmieci są droższe niż brak robienia tego w ogóle. Ponieważ jesteśmy w pętli, jest to więcej niż istotne.
Louis CAD

1
Zgodziłem się również z poglądem Knutha, ale jego punkt widzenia nie zawsze jest prawdziwy. Dodanie tylko jednej linii w celu potencjalnego zwiększenia liczby cykli procesora i pamięci nie jest absolutnie złe. Zabierasz ten pomysł zbyt prosto. Zauważ, że pętla jest zwykle powtarzana wiele razy. Pętla może być powtarzana tysiące razy, co może pochłaniać cenne megabajty na telefonie komórkowym (a także na serwerze lub komputerze), jeśli nie zostanie odpowiednio zoptymalizowane.
Louis CAD

1
Myślę, że oboje dostatecznie jasno przedstawiliśmy nasze punkty widzenia i uważam, że dalsza dyskusja nie będzie korzystna dla tej sekcji komentarzy. Jeśli uważasz, że moja odpowiedź jest niepoprawna, wprowadzająca w błąd lub niekompletna, to ty - lub ktokolwiek - z całą pewnością możesz ją edytować i / lub przegłosować.
Eli Acherkan

5
public void clear(StringBuilder s) {
    s.setLength(0);
}

Stosowanie:

StringBuilder v = new StringBuilder();
clear(v);

ze względu na czytelność uważam, że to najlepsze rozwiązanie.


2

Już dobra odpowiedź. Po prostu dodaj wynik testu porównawczego dla różnicy wydajności StringBuffer i StringBuild, użyj nowej instancji w pętli lub użyj setLength (0) w pętli.

Podsumowanie jest następujące: w dużej pętli

  • StringBuilder jest znacznie szybszy niż StringBuffer
  • Tworzenie nowej instancji StringBuilder w pętli nie różni się od metody setLength (0). (setLength (0) ma bardzo, bardzo małą przewagę niż tworzenie nowej instancji.)
  • StringBuffer jest wolniejszy niż StringBuilder, tworząc nowe wystąpienie w pętli
  • setLength (0) StringBuffer jest znacznie wolniejsza niż tworzenie nowej instancji w pętli.

Bardzo prosty test porównawczy (po prostu ręcznie zmieniłem kod i wykonałem inny test):

public class StringBuilderSpeed {
public static final char ch[] = new char[]{'a','b','c','d','e','f','g','h','i'};

public static void main(String a[]){
    int loopTime = 99999999;
    long startTime = System.currentTimeMillis();
    StringBuilder sb = new StringBuilder();
    for(int i = 0 ; i < loopTime; i++){
        for(char c : ch){
            sb.append(c);
        }
        sb.setLength(0);
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Time cost: " + (endTime - startTime));
}

}

Nowe wystąpienie StringBuilder w pętli: Koszt czasu: 3693, 3862, 3624, 3742

StringBuilder setLength: Time cost: 3465, 3421, 3557, 3408

Nowa instancja StringBuffer w pętli: Koszt czasu: 8327, 8324, 8284

StringBuffer setLength Koszt czasu: 22878, 23017, 22894

Ponownie StringBuilder setLength, aby upewnić się, że mój labtop nie ma problemu z używaniem tak długiego zestawu StringBuffer setLength :-) Koszt czasu: 3448


0
StringBuffer sb = new SringBuffer();
// do something wiht it
sb = new StringBuffer();

Myślę, że ten kod jest szybszy.


3
Będzie to miało problemy z wydajnością. Tworzy nowy obiekt po każdej iteracji
Aditya Singh

@AdityaSingh To całkowicie rozsądne podejście. Nie zakładaj problemów z wydajnością bez dowodów.
shmosel

Zakładanie rzeczy w oparciu o zrozumienie tego, co się dzieje, jest podstawą inteligencji.
rghome
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.