W3docs

Java StringBuilder

Construisez des chaînes mutables efficacement en Java avec StringBuilder — append, insert, reverse et bien plus encore.

Une String est immuable ; l'agrandir avec += dans une boucle est quadratique. StringBuilder est la réponse de la bibliothèque standard : un seul objet avec un tampon interne redimensionnable que vous pouvez compléter, insérer, supprimer et inverser en place, puis convertir en une String immuable une seule fois à la fin. C'est le cheval de bataille derrière tous les patterns modernes de construction de chaînes dans le JDK, et la bonne solution dès que vous vous retrouvez à accumuler du texte dans une boucle ou entre plusieurs appels de méthode.

Construire un builder

StringBuilder sb = new StringBuilder();             // empty, capacity 16
StringBuilder withCap = new StringBuilder(1024);    // empty, preallocated capacity
StringBuilder fromText = new StringBuilder("hi ");  // capacity = length + 16

Le constructeur sans argument démarre avec une capacité de 16, ce qui convient pour les résultats courts mais est un choix pessimiste pour les résultats longs. Si vous connaissez approximativement la taille finale de la chaîne, passez la capacité dès le départ — chaque agrandissement évité représente une allocation de tableau et une copie en moins. new StringBuilder(estimatedLength) est la micro-optimisation la plus efficace dans toute cette partie du livre.

Chaînage : chaque mutateur retourne this

Chaque méthode de mutation sur StringBuilder retourne le builder lui-même, ce qui permet de composer les appels en une seule expression :

String greeting = new StringBuilder()
    .append("Hello, ")
    .append(name)
    .append('!')
    .append('\n')
    .toString();

C'est une convention, pas de la magie ; vous pourriez tout écrire en plusieurs instructions distinctes sans aucune différence fonctionnelle. Le style chaîné reflète la façon dont le compilateur réécrit les enchaînements + en interne, ce qui explique pourquoi cette forme paraît naturelle en Java.

La famille des mutateurs

StringBuilder offre une surface petite et ciblée :

  • append — ajoute du texte ou tout primitif à la fin. Surchargé pour chaque primitif, char[], CharSequence et Object (appelle toString).
  • insert(offset, ...) — mêmes surcharges, mais à une position arbitraire.
  • delete(start, end), deleteCharAt(i) — supprime une plage ou un seul caractère.
  • replace(start, end, replacement) — remplace une plage par une nouvelle sous-chaîne ; les longueurs peuvent différer.
  • reverse() — inverse le tampon en place.
  • setCharAt(i, ch) — réécrit un seul caractère.
  • setLength(n) — tronque (ou complète avec ) à n caractères.

Ces méthodes modifient le tampon ; elles ne retournent pas une nouvelle String. Pour obtenir un instantané du contenu sous forme de chaîne immuable, appelez toString().

Inspection et conversion

  • length() — nombre de caractères courant.
  • capacity() — taille actuelle du tableau interne. Toujours ≥ length().
  • charAt(i), substring(start) / substring(start, end) — accès en lecture, identique à String.
  • indexOf(s), lastIndexOf(s) — localise une sous-chaîne.
  • toString() — produit une String immuable. Appelez-la une seule fois à la fin ; l'appeler plusieurs fois pendant la construction est un gaspillage d'allocation.
  • ensureCapacity(n) — pré-agrandit le tampon à au moins n.
  • trimToSize() — réduit le tampon pour correspondre au contenu actuel. Rarement nécessaire.

Comment le tampon grandit

En interne, StringBuilder contient un byte[] (ou char[] sur les anciens JDK). Lorsqu'un append provoquerait un débordement, le tampon est réalloué à environ 2 × oldCapacity + 2, et l'ancien contenu est copié. Chaque agrandissement est O(n) par rapport à la taille actuelle, mais le schéma de doublement rend le coût total de n appends O(n) amorti — très différent de la concaténation répétée de String, où la même boucle est O(n²).

append "a" — capacity 16, length 1
... 15 more — capacity 16, length 16
append "b" — grow to 34, length 17
... 17 more — capacity 34, length 34
append "c" — grow to 70, length 35

Si vous connaissez la longueur finale, vous évitez toutes ces réallocations en construisant directement avec cette capacité.

StringBuilder vs l'opérateur +

Pour un assemblage de chaînes court et statiquement connu, le compilateur fait ce qu'il faut tout seul. "Hello, " + name + "!" est réécrit à la compilation soit en une seule chaîne StringBuilder, soit en un appel à StringConcatFactory.makeConcatWithConstants (Java 9+). Les deux sont efficaces. Il n'est pas nécessaire de micro-gérer ces expressions.

Le pattern à éviter est += à l'intérieur d'une boucle sur un nombre inconnu d'itérations :

// O(n²) — every += allocates a new String holding everything seen so far
String out = "";
for (String token : tokens) {
  out += token + "|";
}

// O(n) — one buffer, one final String
StringBuilder sb = new StringBuilder();
for (String token : tokens) {
  sb.append(token).append('|');
}
String out = sb.toString();

Si la boucle s'exécute quelques fois, la différence est invisible. À quelques milliers de tokens, c'est un problème mesurable. À un million, la première forme est un blocage et la seconde prend quelques millisecondes.

StringBuilder n'est pas thread-safe

StringBuilder omet délibérément la synchronisation pour être rapide dans le cas le plus courant — le cas mono-thread. Si deux threads ajoutent des données au même builder simultanément, les résultats sont indéfinis : écritures perdues, caractères écrasés, ou ArrayIndexOutOfBoundsException provenant du chemin d'agrandissement. Dans le cas rare où un builder est partagé entre threads, utilisez plutôt le jumeau synchronisé — StringBuffer. En pratique, vous ne partagez presque jamais un builder ; chaque thread construit le sien.

Un exemple concret

Le programme ci-dessous utilise toutes les parties de la surface qui importent dans le code quotidien : append chaîné, un insert explicite, un replace qui modifie la longueur d'une plage, un reverse, et un seul toString final. La capacité est affichée au début et à la fin pour rendre visible le doublement du tampon.

java— editable, runs on the server

Observez les valeurs de capacité dans la sortie (capacity=32 au départ, capacity=66 à la fin). Chaque append de la chaîne tient dans la capacité initiale de 32 — la chaîne construite ne fait que 24 caractères. L'insert et le replace poussent ensuite la longueur à 40, ce qui dépasse 32 et déclenche exactement un agrandissement (à 2 × 32 + 2 = 66). Dimensionner plus précisément selon la vraie longueur finale — new StringBuilder(48) ici — aurait évité cette unique réallocation. C'est tout le jeu : meilleure est votre estimation de capacité, moins il y a d'événements de copie lors de l'agrandissement.

Et ensuite

StringBuilder est rapide parce qu'il n'est pas thread-safe. Son jumeau synchronisé est la bonne réponse dans le cas (rare) où vous souhaitez vraiment partager un tampon entre threads — même API, méthodes avec verrouillage. Continuez avec Java StringBuffer.

Exercices pratiques

Pratique
Pourquoi `StringBuilder` est-il généralement préféré à la concaténation de `String` dans une boucle ?
Pourquoi `StringBuilder` est-il généralement préféré à la concaténation de `String` dans une boucle ?
Was this page helpful?