Drôle de sujet me diriez-vous n’est-ce pas? Je vous l’accorde, concaténer des chaines de caractères c’est quelque chose de tellement basique, naturel, comme additionner deux nombres, qu’on ai difficilement emmené à penser qu’il y a une meilleure façon de faire; pourtant c’est bien, le cas. Dans cet article(qui est le premier du blog en passant), je vais m’atteler à vous montrer tout d’abord les 3 principales méthodes existantes en Java pour concaténer les chaines de caractères et à partir d’exemples concrets on verra ensemble quelle est la meilleure méthode à utiliser en fonction du context.
La théorie
Avant de voir la différence en action, les plus impatients(comme moi 🙂 ), sont priés de prendre leur mal en patience.
Les chaines de caractères en Java (String)
Les chaines de caractères en java sont représentées par la classe String.
On peut instancier une chaine de caractère de deux façons différentes
String chaine = "toto";
String chaine = new String("toto");
Tous les objets de la classe String sont immutables, en gros une fois instantiés ils ne peuvent être modifiés, et chaque fois que tu changes leur valeur, en fait en interne tu crée un nouvel objet. Cette propriété de la classe String lui permet d’être facilement partagée entre plusieurs threads ou fonctions.
Lorsque nous créons un String en utilisant les doubles quotes, La JVM recherche dans son String Pool (Oui java garde un pool de chaines de caractères pendant l’execution du programme), si une chaine de caractère identique est trouvée, sa référence est retournée, sinon une nouvelle chaine est crée et mise dans le String Pool. De cette façon la JVM fait pas mal d’économies en terme de mémoire. Par contre chaque fois que l’opérateur new est utilisé, c’est bien un nouvel objet qui est crée en mémoire. Conclusion chaque fois que vous le pouvez utilisez de préférence les doubles quotes.
La classe String surcharge l’opérateur + et permet de concaténer deux chaines de caractères. Mais en interne c’est bien la classe StringBuffer qui est utilisée.
La classe String surcharge également les méthodes equals() et hashCode() de la classe Object. Deux chaines de caractères sont égales si et seulement si elles ont les mêmes caractères dans le même ordre. Il faut noter que la méthode equals() est sensible à la casse, et si vous souhaitez faire une comparaison en ignorant la casse, utilisez plutôt la méthode equalsIgnoreCase().
La class String est une classe final vous ne pouvez donc pas hériter de celle-ci
La classe String stock les chaines de caractères sous l’encode UTF-16 (J’écrirais un article sur l’encode en Java, si vous avez un peu d’expérience en Java, vous savez que ça peut devenir un véritable casse-tête)
Voilà qui introduit bien les choses, si vous avez pris la peine de lire attentivement vous aurez compris qu’en fait il existe uniquement deux méthodes pour concaténer les chaines de caractères en Java (StringBuilder et StringBuffer) la surcharge de l’opérateur + utilisant StringBuffer en interne.
StringBuffer vs StringBuilder
Jusqu’à la version 1.4 de java StringBuffer était la seule option possible pour la manipulation des chaines de caractères. Cette classe a un principal inconvénient: toutes ses méthodes publiques sont synchronisées (Pour rappel une méthode d’une classe est synchronisée si lors de l’execution de celle-ci dans un thread, aucun autre thread ne peut avoir la main sur l’objet) et de ce fait lorsque la taille des données à traiter augmente celle-ci peut s’avérer lente. La version 1.5 de Java a introduit une nouvelle classe, StringBuilder qui est similaire à StringBuffer à l’exception du fait qu’elle ne gère pas les Threads, en gros deux threads différents peuvent modifier le même objet simultanément, ce qui peu créer des incohérences.
Conclusion
Pour récapituler si vous êtes dans un environnement mono-thread ou si vous ne souciez pas de l’isolation des threads, il vaut mieux utiliser StringBuilder sinon utilisez StringBuffer.
La pratique
Si vous êtes arrivés jusqu’ici après avoir lu toute la théorie alors, félicitations vous commencez à être patients, et votre patience sera récompensée, les autres, je n’ai aucun mot pour 🙁 .
Afin d’illustrer un peu tout ce qui a été dit plus haut nous allons nous servir du programme ci-dessous et alterner respectivement entre StringBuilder et StringBuffer pour différents nombres d’itération i (1000, 10000, 100000) nous observerons les performances en terme de temps d’execution (temps en millissecondes, mémoire en bytes).
package com.carnetdudev.java; import java.util.GregorianCalendar; public class TestString { public static void main(String[] args) { System.gc(); long start=new GregorianCalendar().getTimeInMillis(); long startMemory=Runtime.getRuntime().freeMemory(); //StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder(); for(int i = 0; i<100000; i++){ sb.append(":"+i); sb.insert(i, "Hi"); } long end=new GregorianCalendar().getTimeInMillis(); long endMemory=Runtime.getRuntime().freeMemory(); System.out.println("Time Taken:"+(end-start)); System.out.println("Memory used:"+(startMemory-endMemory)); } }
Le programme est exécuté sur une machine ayant les spécifications suivantes – OS: MacOS High Sierra
– Processeur: 2,2 GhZ Intel Core i7
– Mémoire: 16Go
La version de Java utilisée pour cet exemple est la 1.8
Valeur de i | 1000 | 10000 | 10000 |
---|---|---|---|
StringBuffer | (2, 0) | (25,1489672) | (3077, 17698416) |
StringBuilder | (2, 0) | (25, 1489648) | (3025, 17698392) |
Comparaison des performances de StringBuilder et StringBuffer pour différents nombre d’itérations
Comme on peut le voir dans le tableau ci-dessus les deux classes se comportent de façon quasi similaires tant que le nombre d’itération est faible, plus on augmente le nombre d’itération plus StringBuffer semble plus lent, ce qui est normal étant donné que ses méthodes sont synchronisées.
Leave a Reply