Supprimer le dernier caractère d'un StringBuilder?


423

Lorsque vous devez parcourir une collection et créer une chaîne de chaque donnée séparée par un délimiteur, vous vous retrouvez toujours avec un délimiteur supplémentaire à la fin, par exemple

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

Donne quelque chose comme: serverId_1, serverId_2, serverId_3,

Je voudrais supprimer le dernier caractère du StringBuilder (sans le convertir car j'en ai toujours besoin après cette boucle).


11
Si en joignant des chaînes vous voulez dire "concaténation de chaînes", cela dépend du nombre de chaînes et de leurs longueurs. L'utilisation d'un générateur de chaînes est plus efficace si vous allez marteler un grand nombre de chaînes, quelle que soit leur taille, car les chaînes sont immuables. Chaque fois que vous concaténez des chaînes ensemble, vous créez une nouvelle chaîne résultante (qui est en réalité un tableau de caractères). Les générateurs de chaînes sont essentiellement une liste de caractères qui ne deviennent une chaîne immuable que lorsque vous appelez la méthode toString ().
dyslexicanaboko

4
Si vous utilisez Java 8, utilisez simplement StringJoiner: stackoverflow.com/a/29169233/901641
ArtOfWarfare

Réponses:


640

D'autres ont souligné la deleteCharAtméthode, mais voici une autre approche alternative:

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

Sinon, utilisez la Joinerclasse de Guava :)

Depuis Java 8, StringJoinerfait partie du JRE standard.


7
@Coronatus: Non, car "" est l' absence de tout caractère, pas un seul caractère.
Jon Skeet

31
n'exécutera pas prefix = ","; chaque cycle de boucle affecte les performances?
Harish

21
@Harish: Peut-être, un tout petit peu - très peu probable cependant.
Jon Skeet

4
@Harish - et peut-être pas du tout, si l'optimiseur déroule la première itération de boucle.
Stephen C

6
Apache Commons a fait une autre alternative à la goyave est Joinertrop dans leur StringUtils. commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/… , java.lang.String)
GoRoS

419

Une autre solution simple est:

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

Une solution plus compliquée:

La solution ci-dessus suppose que sb.length() > 0... c'est-à-dire qu'il y a un "dernier caractère" à supprimer. Si vous ne pouvez pas faire cette hypothèse, et / ou vous ne pouvez pas traiter l'exception qui se produirait si l'hypothèse est incorrecte, vérifiez d'abord la longueur du StringBuilder; par exemple

// 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
Très belle solution. Impact le plus faible sur les performances et le moins de code requis :)
Alain O'Dea

186
if(sb.length() > 0){
    sb.deleteCharAt(sb.length() - 1);
}

33
Cela est trop voté mais ce n'est pas efficace, il fait un system.arraycopy. Ce que @Rohit Reddy Korrapolu a dit.
alianos-

13
C'est dangereux pour sb.length() == 0aussi
Matthias

Est-ce sûr avec des personnages de paire de substitution en jeu?
rogerdpack

En supposant que le dernier caractère est le séparateur de virgules (comme dans l'exemple), les substituts ne font aucune différence. Si vous devez généraliser, puis soustrayez separator.length()au lieu de 1.
Stephen C

61

Depuis Java 8, la classe String possède une méthode statique join. Le premier argument est une chaîne que vous voulez entre chaque paire de chaînes, et le second est un Iterable<CharSequence>(qui sont les deux interfaces, donc quelque chose comme ça List<String>fonctionne. Donc vous pouvez simplement faire ceci:

String.join(",", serverIds);

Toujours en Java 8, vous pouvez utiliser la nouvelle StringJoinerclasse, pour les scénarios où vous souhaitez commencer à construire la chaîne avant d'avoir la liste complète des éléments à y mettre.


nvm édité pour vous si cela ne vous dérange pas, a également supprimé mon commentaire
Eugene

@Eugene - J'ai réécrit complètement la réponse pour me concentrer sur String.joinau lieu de StringJoiner.
ArtOfWarfare

37

Obtenez simplement la position de la dernière occurrence de caractère.

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

Depuis lastIndexOfeffectuera une recherche inversée, et vous savez qu'il trouvera au premier essai, les performances ne seront pas un problème ici.

ÉDITER

Puisque je continue de me lever sur ma réponse (merci les gens 😊), cela vaut la peine de le noter:

Sur Java 8 , il serait juste plus lisible et explicite d'utiliser StringJoiner . Il a une méthode pour un simple séparateur et une surcharge pour le préfixe et le suffixe.

Exemples tirés d'ici: exemple

Exemple utilisant un simple séparateur:

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

Production:

Logan-Magneto-Rogue-Storm

Exemple avec suffixe et préfixe:

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

Production

(Negan, Rick, Maggie, Daryl)


Vous serez sûr que le dernier caractère est un ,car il s'agissait de la dernière instruction du for loop. C'est lastInfexOfplus pour la lisibilité et pour en faire une évidence si vous ne voulez pas vous souvenir s'il est indexé 0 ou non. De plus, vous n'avez pas besoin de vous mêler de la longueur du constructeur de cordes. C'est juste pour la commodité.
Reuel Ribeiro

34

Dans ce cas,

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

est préférable car il attribue simplement la dernière valeur à '\0'tandis que la suppression du dernier caractère neSystem.arraycopy


1
L' setLengthappel n'affecte rien à la dernière valeur. Les tampons de chaîne Java ne se terminent pas par zéro / zéro. En fait, il setLengths'agit simplement de mettre à jour un lengthchamp.
Stephen C

@Rohit Reddy Korrapolu: Mais les arraycopyéléments copiés 0, donc je suppose que cela peut être optimisé.
maaartinus

2
Si l'argument newLength est supérieur ou égal à la longueur actuelle, suffisamment de caractères nuls ('\ u0000') sont ajoutés pour que la longueur devienne l'argument newLength. Ce qui n'est pas le cas.
fglez


11

Une autre alternative

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

2
Cela devrait être mieux que de supprimer le dernier caractère car cela nécessite des calculs de taille. À moins que la suppression du premier caractère ne provoque le déplacement des données ...
slott

8

Alternativement,

StringBuilder result = new StringBuilder();
for(String string : collection) {
    result.append(string);
    result.append(',');
}
return result.substring(0, result.length() - 1) ;

Utilisable car vous pouvez ajouter un "." à la fin.
astucieusement conçu

6
StringBuilder sb = new StringBuilder();
sb.append("abcdef");
sb.deleteCharAt(sb.length() - 1);
assertEquals("abcde",sb.toString());
// true

5

Encore une autre alternative:

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

Pour éviter de réinitier (affecter les performances) d' prefixutilisation TextUtils.isEmpty:

            String prefix = "";
            for (String item : list) {
                sb.append(prefix);
                if (TextUtils.isEmpty(prefix))
                    prefix = ",";
                sb.append(item);
            }

À quel type de package appartient TestUtils?
Markus

@Markus android.text.TextUtils
NickUnuchek

1

Vous pouvez essayer d'utiliser la classe «Joiner» au lieu de supprimer le dernier caractère de votre texte généré;

                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

Je fais quelque chose comme ci-dessous:

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

0

Voici une autre solution:

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

String resultingString = "";
if ( sb.length() > 1 ) {
    resultingString = sb.substring(1);
}

1
Oh je vois. Vous appelez la sous-chaîne sur le StringBuilder pas une chaîne.
Stephen C

Mais de toute façon, ce n'est qu'une petite variante de la solution de Zaki à partir de 2010.
Stephen C

0

Personnellement, j'aime ajouter un caractère de retour arrière (ou plus pour un "délimiteur" plus long) à la fin:

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

sb.append('\b');

Notez qu'il a des problèmes:

  • comment \best affiché dépend de l'environnement,
  • le length()du Stringcontenu peut être différent de la longueur des caractères « Visibile »

Lorsque cela \bsemble OK et que la longueur n'a pas d'importance, comme la connexion à une console, cela me semble assez bon.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.