Usunąć ostatni znak StringBuilder?

422

Gdy musisz przejrzeć kolekcję i utworzyć ciąg danych oddzielonych separatorem, zawsze kończy się to dodatkowym separatorem na końcu, np.

for (String serverId : serverIds) {
  sb.append(serverId);
   sb.append(",");
}

Daje coś w rodzaju: serverId_1, serverId_2, serverId_3,

Chciałbym usunąć ostatni znak w StringBuilder (bez konwersji, ponieważ nadal potrzebuję go po tej pętli).

Mateusz
źródło
11
Jeśli łącząc łańcuchy, masz na myśli „łączenie łańcuchów”, zależy to od liczby łańcuchów i ich długości. Korzystanie z konstruktora ciągów jest bardziej wydajne, jeśli zamierzasz wbijać wiele ciągów, niezależnie od ich wielkości, ponieważ ciągi są niezmienne. Za każdym razem, gdy łączysz łańcuchy razem, tworzysz nowy łańcuch wynikowy (który jest tak naprawdę tablicą znaków). Konstruktory ciągów są w zasadzie listą znaków, która nie staje się niezmiennym ciągiem dopóki nie wywołasz metody toString ().
dyslexicanaboko
4
Jeśli używasz Java 8, po prostu użyj StringJoiner: stackoverflow.com/a/29169233/901641
ArtOfWarfare

Odpowiedzi:

640

Inni wskazali tę deleteCharAtmetodę, ale oto inne alternatywne podejście:

String prefix = "";
for (String serverId : serverIds) {
  sb.append(prefix);
  prefix = ",";
  sb.append(serverId);
}

Alternatywnie skorzystaj z Joinerklasy z Guawy :)

Począwszy od Java 8, StringJoinerjest częścią standardowego środowiska JRE.

Jon Skeet
źródło
7
@Coronatus: Nie, ponieważ „” oznacza brak jakichkolwiek znaków, a nie pojedynczy znak.
Jon Skeet
31
nie wykona przedrostka = ","; każdy cykl pętli wpływa na wydajność?
Harish
21
@Harish: Być może mały, drobny kawałek - choć mało prawdopodobne, aby był znaczący.
Jon Skeet
4
@Harish - i być może wcale nie, jeśli optymalizator rozwija pierwszą iterację pętli.
Stephen C
6
Apache Commons ma innej alternatywy dla Guava jest Joinerteż w ich StringUtils. commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/… , java.lang.String)
GoRoS 28.09.2013
419

Innym prostym rozwiązaniem jest:

sb.setLength(sb.length() - 1);

Bardziej skomplikowane rozwiązanie:

Powyższe rozwiązanie zakłada, że sb.length() > 0... tzn. Istnieje „ostatni znak” do usunięcia. Jeśli nie możesz przyjąć tego założenia i / lub nie możesz poradzić sobie z wyjątkiem, który powstałby, gdyby założenie było niepoprawne, najpierw sprawdź długość StringBuilder; na przykład

// Readable version
if (sb.length() > 0) {
   sb.setLength(sb.length() - 1);
}

lub

// Concise but harder-to-read version of the above.
sb.setLength(Math.max(sb.length() - 1, 0));
Stephen C.
źródło
23
Bardzo fajne rozwiązanie. Najniższy wpływ na wydajność i najmniejszy wymagany kod :)
Alain O'Dea,
186
if(sb.length() > 0){
    sb.deleteCharAt(sb.length() - 1);
}
bragboy
źródło
33
To jest zbyt wysoko oceniane, ale nie jest wydajne, robi system.arraycopy. Co powiedział @Rohit Reddy Korrapolu.
alianos-
13
Nie jest to sb.length() == 0również niebezpieczne
Matthias
Czy to bezpieczne w przypadku postaci z parami zastępczymi w grze?
rogerdpack,
Zakładając, że ostatnim znakiem jest separator przecinków (jak w przykładzie), to surogaty nie mają znaczenia. Jeśli chcesz uogólnić, odejmij separator.length()zamiast 1.
Stephen C
61

Od wersji Java 8 klasa String ma metodę statyczną join. Pierwszy argument to ciąg znaków, który chcesz między każdą parą ciągów, a drugi to Iterable<CharSequence>(które są obydwoma interfejsami, więc coś w rodzaju List<String>działa. Więc możesz po prostu to zrobić:

String.join(",", serverIds);

Również w Javie 8 możesz użyć nowej StringJoinerklasy do scenariuszy, w których chcesz zacząć konstruować ciąg, zanim będziesz mieć pełną listę elementów do umieszczenia w nim.

ArtOfWarfare
źródło
nvm edytowane dla ciebie, jeśli nie masz nic przeciwko, usunąłem również mój komentarz
Eugene
@Eugene - całkowicie przepisałem odpowiedź, aby się skoncentrować String.joinzamiast StringJoiner.
ArtOfWarfare
37

Wystarczy uzyskać pozycję ostatniego wystąpienia postaci.

for(String serverId : serverIds) {
 sb.append(serverId);
 sb.append(",");
}
sb.deleteCharAt(sb.lastIndexOf(","));

Ponieważ lastIndexOfprzeprowadzi wyszukiwanie wsteczne i wiesz, że znajdzie się przy pierwszej próbie, wydajność nie będzie tutaj problemem.

EDYTOWAĆ

Ponieważ ciągle podnoszę swoją odpowiedź (dzięki ludzie 😊), warto wziąć pod uwagę, że:

W Javie 8 i nowszej byłoby po prostu bardziej czytelne i wyraźne korzystanie z StringJoiner . Ma jedną metodę prostego separatora oraz przeciążenie prefiksu i sufiksu.

Przykłady zaczerpnięte stąd: przykład

Przykład użycia prostego separatora:

    StringJoiner mystring = new StringJoiner("-");    

    // Joining multiple strings by using add() method  
    mystring.add("Logan");  
    mystring.add("Magneto");  
    mystring.add("Rogue");  
    mystring.add("Storm");  

    System.out.println(mystring);

Wynik:

Logan-Magneto-Rogue-Storm

Przykład z przyrostkiem i przedrostkiem:

    StringJoiner mystring = new StringJoiner(",", "(", ")");    

    // Joining multiple strings by using add() method  
    mystring.add("Negan");  
    mystring.add("Rick");  
    mystring.add("Maggie");  
    mystring.add("Daryl");  

    System.out.println(mystring);

Wynik

(Negan, Rick, Maggie, Daryl)

Reuel Ribeiro
źródło
Będziesz pewien, że ostatnia postać jest, ,ponieważ była to ostatnia wypowiedź for loop. Chodzi lastInfexOfo czytelność i sprawia, że ​​jest to łatwe, jeśli nie chcesz pamiętać, czy jest indeksowane na 0, czy nie. Co więcej, nie musisz wtrącać się w długość łańcucha znaków. To tylko dla wygody.
Reuel Ribeiro
34

W tym przypadku,

sb.setLength(sb.length() - 1);

jest preferowane, ponieważ po prostu przypisuje ostatnią wartość, '\0'podczas gdy usuwa ostatni znakSystem.arraycopy

Rohit Reddy Korrapolu
źródło
1
setLengthPołączenie nie jest przypisywanie nic do ostatniej wartości. Bufory ciągów Java nie są zakończone zerem / zerem. W rzeczywistości setLengthto po prostu aktualizacja lengthpola.
Stephen C
@Rohit Reddy Korrapolu: Ale arraycopykopiuje 0 elementów, więc myślę, że można go zoptymalizować.
maaartinus,
2
Jeśli argument newLength jest większy lub równy bieżącej długości, dołączane są wystarczające znaki null („\ u0000”), aby długość stała się argumentem newLength. Co nie jest prawdą.
fglez
11

Kolejna alternatywa

for(String serverId : serverIds) {
   sb.append(",");
   sb.append(serverId); 
}
sb.deleteCharAt(0);
Rafiq
źródło
2
Powinno być lepsze niż usunięcie ostatniego znaku, ponieważ wymaga to obliczenia rozmiaru. Chyba że usunięcie pierwszego znaku spowoduje, że dane będą przenoszone ...
slott,
8

Alternatywnie,

StringBuilder result = new StringBuilder();
for(String string : collection) {
    result.append(string);
    result.append(',');
}
return result.substring(0, result.length() - 1) ;
Zaki
źródło
Użyteczne, ponieważ możesz dodać „.” na końcu.
zręcznie wykonał
6
StringBuilder sb = new StringBuilder();
sb.append("abcdef");
sb.deleteCharAt(sb.length() - 1);
assertEquals("abcde",sb.toString());
// true
Antoine
źródło
5

Jeszcze jedna alternatywa:

public String join(Collection<String> collection, String seperator) {
    if (collection.isEmpty()) return "";

    Iterator<String> iter = collection.iterator();
    StringBuilder sb = new StringBuilder(iter.next());
    while (iter.hasNext()) {
        sb.append(seperator);
        sb.append(iter.next());
    }

    return sb.toString();
}
Jason Day
źródło
3

Aby uniknąć prefixponownego uruchomienia (wpłynąć na wydajność) użycia TextUtils.isEmpty:

            String prefix = "";
            for (String item : list) {
                sb.append(prefix);
                if (TextUtils.isEmpty(prefix))
                    prefix = ",";
                sb.append(item);
            }
NickUnuchek
źródło
Do jakiego pakietu należy TestUtils?
Markus
@Markus android.text.TextUtils
NickUnuchek
1

Możesz spróbować użyć klasy „Joiner” zamiast usuwać ostatni znak z wygenerowanego tekstu;

                List<String> textList = new ArrayList<>();
                textList.add("text1");
                textList.add("text2");
                textList.add("text3");

                Joiner joiner = Joiner.on(",").useForNull("null");
                String output = joiner.join(textList);

               //output : "text1,text2,text3"
oguzhan
źródło
1

Robię coś takiego:

    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < value.length; i++) {
        stringBuilder.append(values[i]);
        if (value.length-1) {
            stringBuilder.append(", ");
        }
    }
Vikasdeep Singh
źródło
0

Oto inne rozwiązanie:

for(String serverId : serverIds) {
   sb.append(",");
   sb.append(serverId); 
}

String resultingString = "";
if ( sb.length() > 1 ) {
    resultingString = sb.substring(1);
}
Stephan
źródło
1
Rozumiem. Wywołujesz podciąg na StringBuilder, a nie na String.
Stephen C
Ale tak czy inaczej, jest to tylko niewielki wariant rozwiązania Zaki z 2010 roku.
Stephen C
0

Osobiście lubię dodawać znak backspace (lub więcej dla dłuższego „separatora”) na końcu:

for(String serverId : serverIds) {
    sb.append(serverId);
    sb.append(",");
}

sb.append('\b');

Pamiętaj, że ma problemy:

  • sposób \bwyświetlania zależy od środowiska,
  • length()o Stringtreści może być różna od długości znaków „widoczny”

Kiedy \bwygląda OK, a długość nie ma znaczenia, np. Logowanie do konsoli, wydaje mi się to wystarczająco dobre.

Atakujący
źródło
-1

stringBuilder.Remove (stringBuilder.Length - 1, 1);

Mohamed Farook Mohamed Fazrin
źródło