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


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.


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

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

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.


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)


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


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

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

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

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

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

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"

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(", ");
        }
    }

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

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.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.