Remover o último caractere de um StringBuilder?

423

Quando você precisa percorrer uma coleção e criar uma sequência de cada dado separada por um delimitador, você sempre acaba com um delimitador extra no final, por exemplo,

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

Fornece algo como: serverId_1, serverId_2, serverId_3,

Gostaria de excluir o último caractere no StringBuilder (sem convertê-lo, porque ainda preciso dele após esse loop).

Mateus
fonte
11
Se, ao unir cadeias, você quer dizer "concatenação de cadeias", isso depende do número de cadeias e de seus comprimentos. O uso de um construtor de cordas é mais eficiente se você estiver martelando várias cordas, independentemente do seu tamanho, pois as cordas são imutáveis. Toda vez que você concatena as seqüências, você cria uma nova sequência resultante (que é realmente uma matriz de caracteres). Os construtores de strings são essencialmente uma lista de caracteres que não se tornam imutáveis ​​até você chamar o método toString ().
Dislexicanaboko
4
Se você estiver usando Java 8, basta usar StringJoiner: stackoverflow.com/a/29169233/901641
ArtOfWarfare

Respostas:

640

Outros apontaram o deleteCharAtmétodo, mas aqui está outra abordagem alternativa:

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

Como alternativa, use a Joinerclasse Guava :)

A partir do Java 8, StringJoinerfaz parte do JRE padrão.

Jon Skeet
fonte
7
@ Coronatus: Não, porque "" é a ausência de caracteres, não um caractere único.
precisa saber é o seguinte
31
não executará o prefixo = ","; todo ciclo de loop afeta o desempenho?
Harish
21
@Harish: Possivelmente, um pouquinho, um pouquinho - muito improvável que seja significativo.
Jon Skeet
4
@Harish - e possivelmente nem um pouco, se o otimizador desenrola a primeira iteração do loop.
Stephen C
6
Apache Commons tem outra alternativa a goiaba é Joinermuito em sua StringUtils. commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/… , java.lang.String)
GoRoS
419

Outra solução simples é:

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

Uma solução mais complicada:

A solução acima pressupõe que sb.length() > 0... ou seja, existe um "último caractere" a ser removido. Se você não pode fazer essa suposição, e / ou não pode lidar com a exceção que resultaria se a suposição estiver incorreta, verifique primeiro o comprimento do StringBuilder; por exemplo

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

ou

// Concise but harder-to-read version of the above.
sb.setLength(Math.max(sb.length() - 1, 0));
Stephen C
fonte
23
Solução muito boa. Menor impacto sobre o desempenho e menos código necessário :)
Alain O'Dea
186
if(sb.length() > 0){
    sb.deleteCharAt(sb.length() - 1);
}
bragboy
fonte
33
Isso é muito votado, mas não é eficiente, ele faz um system.arraycopy. O que o @Rohit Reddy Korrapolu disse.
usar o seguinte código
13
É inseguro sb.length() == 0também
Matthias
Isso é seguro com personagens substitutos em jogo?
Rogerdpack 18/03/2015
Supondo que o último caractere seja o separador de vírgulas (como no exemplo), os substitutos não farão diferença. Se você precisar de generalizar em seguida, subtrair separator.length()em vez de 1.
Stephen C
61

No Java 8, a classe String possui um método estático join. O primeiro argumento é uma string que você deseja entre cada par de strings, e o segundo é um Iterable<CharSequence>(que são ambas interfaces, portanto algo como List<String>funciona. Então, você pode fazer isso:

String.join(",", serverIds);

Também no Java 8, você pode usar a nova StringJoinerclasse, para cenários em que deseja começar a construir a sequência antes de ter a lista completa de elementos a serem inseridos.

ArtOfWarfare
fonte
nvm editado para você, se você não se importa, removi meu comentário também
Eugene
@ Eugene - eu reescrevi a resposta completamente para focar em String.joinvez de StringJoiner.
ArtOfWarfare
37

Apenas obtenha a posição da última ocorrência de caractere.

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

Desde a lastIndexOf a pesquisa inversa será executada e você sabe que ela será encontrada na primeira tentativa, o desempenho não será um problema aqui.

EDITAR

Como eu continuo levantando minha resposta (obrigado pessoal 😊), vale a pena:

No Java 8 em diante, seria mais legível e explícito usar o StringJoiner . Ele tem um método para um separador simples e uma sobrecarga para prefixo e sufixo.

Exemplos retirados daqui: exemplo

Exemplo usando separador simples:

    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);

Resultado:

Logan-Magneto-Rogue-Storm

Exemplo com sufixo e prefixo:

    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);

Resultado

(Negan, Rick, Maggie, Daryl)

Reuel Ribeiro
fonte
Você terá certeza de que o último caractere é um ,porque foi a última declaração do for loop. O lastInfexOfé mais para facilitar a leitura e torná-lo um acéfalo, se você não quer se lembrar se for 0 indexada ou não. Além disso, você não precisa interferir no comprimento do construtor de cordas. É apenas para a conveniência.
Reuel Ribeiro
34

Nesse caso,

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

é preferível, pois apenas atribui o último valor ao '\0'passo que excluir o último caractereSystem.arraycopy

Rohit Reddy Korrapolu
fonte
1
A setLengthchamada não está atribuindo nada ao último valor. Os buffers de seqüência de caracteres Java não são nulos / zero finalizados. Na verdade, setLengthé simplesmente atualizar um lengthcampo.
Stephen C
@Rohit Reddy Korrapolu: Mas as arraycopycópias são 0 elementos, então eu acho que isso pode ser otimizado.
Maaartinus 3/11
2
Se o argumento newLength for maior ou igual ao comprimento atual, caracteres nulos suficientes ('\ u0000') serão anexados para que o comprimento se torne o argumento newLength. O que não é o caso.
fglez
11

Outra alternativa

for(String serverId : serverIds) {
   sb.append(",");
   sb.append(serverId); 
}
sb.deleteCharAt(0);
Rafiq
fonte
2
Deve ser melhor do que remover o último caractere, pois isso requer cálculos de tamanho. A menos que a remoção do primeiro caractere faça com que os dados sejam movidos ...
slott
8

Alternativamente,

StringBuilder result = new StringBuilder();
for(String string : collection) {
    result.append(string);
    result.append(',');
}
return result.substring(0, result.length() - 1) ;
Zaki
fonte
Utilizável como você pode adicionar um "." no fim.
usar o seguinte
6
StringBuilder sb = new StringBuilder();
sb.append("abcdef");
sb.deleteCharAt(sb.length() - 1);
assertEquals("abcde",sb.toString());
// true
Antoine
fonte
5

Ainda outra alternativa:

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
fonte
3

Para evitar a reinicialização (afetar o desempenho) do prefixuso TextUtils.isEmpty:

            String prefix = "";
            for (String item : list) {
                sb.append(prefix);
                if (TextUtils.isEmpty(prefix))
                    prefix = ",";
                sb.append(item);
            }
NickUnuchek
fonte
A que tipo de pacote o TestUtils pertence?
Markus
@Markus android.text.TextUtils
NickUnuchek
1

Você pode tentar usar a classe 'Joiner' em vez de remover o último caractere do texto gerado;

                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
fonte
1

Estou fazendo algo como abaixo:

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

Aqui está outra solução:

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

String resultingString = "";
if ( sb.length() > 1 ) {
    resultingString = sb.substring(1);
}
Stephan
fonte
1
Ah eu vejo. Você está chamando substring no StringBuilder e não em String.
Stephen C
Mas de qualquer maneira, esta é apenas uma pequena variante solução de Zaki a partir de 2010.
Stephen C
0

Pessoalmente, gosto de acrescentar um caractere de backspace (ou mais para um "delimitador" mais longo) no final:

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

sb.append('\b');

Observe que ele tem problemas:

  • como \bé exibido depende do ambiente,
  • a length()do Stringconteúdo pode ser diferente do comprimento de caracteres "Visível"

Quando \bparece bom e o comprimento não importa, como fazer login em um console, isso parece bom o suficiente para mim.

Attacktive
fonte
-1

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

Mohamed Farook Mohamed Fazrin
fonte