StringBuilder vs StringBuffer : que choisir et pourquoi est-ce important ?
\n\nSi vous avez déjà écrit du code Java qui manipule intensivement des chaînes de caractères (par exemple, pour construire une requête SQL, générer du JSON ou simplement concaténer des dizaines de fragments de texte), vous avez sûrement été confronté au dilemme : StringBuilder ou StringBuffer ? Sur Stack Overflow, cette question cumule des centaines de milliers de vues, et ce n'est pas un hasard. À première vue, ces classes sont comme des jumeaux : elles étendent toutes deux AbstractStringBuilder, utilisent un tableau de caractères modifiable, et proposent les méthodes append(), insert(), delete(). Mais le diable se cache dans les détails, comme toujours.
Dans cet article, nous ne nous contenterons pas d'énumérer les différences. Nous les analyserons avec des exemples concrets, explorerons les coulisses de la JVM et répondrons aux questions que l'on vous posera inévitablement en entretien. C'est parti !
\n\n1. La différence principale : la synchronisation
\n\nC'est le point le plus important, la raison d'être de toute cette histoire. StringBuffer est thread-safe (sécurisé pour les threads). La quasi-totalité de ses méthodes publiques sont synchronisées avec le mot-clé synchronized. StringBuilder, en revanche, n'est pas synchronisé. Point final.
Regardons le code (version simplifiée du JDK réel) :
\n\n// StringBuffer (Java 17)\npublic synchronized StringBuffer append(String str) {\n toStringCache = null;\n super.append(str);\n return this;\n}\n\n// StringBuilder\npublic StringBuilder append(String str) {\n super.append(str);\n return this;\n}\n\n\nUne seule ligne synchronized fait toute la différence. Mais cette ligne change tout : les performances, le domaine d'application, et même l'architecture de votre application.
Quand utiliser StringBuffer ?
\n- \n
- Vous développez une application multithread. \n
- La même instance de
StringBufferest utilisée par plusieurs threads sans synchronisation externe. \n - Exemple : un logger qui collecte des messages provenant de différents threads dans un seul buffer. \n
Quand utiliser StringBuilder ?
\n- \n
- Code monothread (90 % des cas). \n
- Variables locales à l'intérieur d'une méthode. \n
- Boucles avec concaténation de chaînes (le compilateur vous suggérera lui-même StringBuilder). \n
2. Performances : qui est le plus rapide ?
\n\nIci, rien de surprenant : StringBuilder est plus rapide que StringBuffer. De combien ? Cela dépend de la charge, mais en moyenne de 2 à 5 fois. Vérifions cela avec un simple benchmark.
\n\npublic class PerformanceTest {\n private static final int ITERATIONS = 10_000_000;\n\n public static void main(String[] args) {\n // StringBuilder\n long start = System.nanoTime();\n StringBuilder sb = new StringBuilder();\n for (int i = 0; i < ITERATIONS; i++) {\n sb.append("a");\n }\n long sbTime = System.nanoTime() - start;\n\n // StringBuffer\n start = System.nanoTime();\n StringBuffer sbf = new StringBuffer();\n for (int i = 0; i < ITERATIONS; i++) {\n sbf.append("a");\n }\n long sbfTime = System.nanoTime() - start;\n\n System.out.println("StringBuilder : " + sbTime / 1_000_000 + " ms");\n System.out.println("StringBuffer : " + sbfTime / 1_000_000 + " ms");\n System.out.println("StringBuffer est " + (sbfTime / (double) sbTime) + " fois plus lent");\n }\n}\n\n\nSur mon ordinateur portable (Intel i7, 16 Go de RAM, Java 17), le résultat est constant : StringBuilder ~180 ms, StringBuffer ~620 ms. La différence est de près de 3,5 fois. Et si l'on ajoute du travail réel (analyse, formatage), l'écart peut encore se creuser. Pourquoi ? Parce que chaque appel à synchronized implique une vérification du moniteur, une acquisition de verrou et sa libération. Dans un code monothread, c'est une pure perte de temps.