247 votes

Devrais-je utiliser String.format() de Java si les performances sont importantes ?

Nous devons constamment construire des chaînes de caractères pour la sortie des journaux, etc. Au fil des versions du JDK, nous avons appris à utiliser les éléments suivants StringBuffer (nombreux ajouts, thread safe) et StringBuilder (nombreux ajouts, non sécurisé par les threads).

Quel est le conseil pour utiliser String.format() ? Est-elle efficace, ou sommes-nous obligés de nous en tenir à la concaténation pour les phrases d'une ligne lorsque les performances sont importantes ?

Par exemple, le vieux style laid,

String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";

contre un nouveau style soigné (String.format, qui est peut-être plus lent),

String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);

Remarque : mon cas d'utilisation spécifique concerne les centaines de chaînes d'enregistrement en une seule ligne dans mon code. Elles n'impliquent pas de boucle, donc StringBuilder est trop lourd. Je suis intéressé par String.format() spécifiquement.

32 votes

Pourquoi ne pas le tester ?

1 votes

Si vous produisez cette sortie, je suppose qu'elle doit être lisible par un humain à un rythme qui lui est propre. Disons 10 lignes par seconde au maximum. Je pense que vous trouverez que l'approche que vous prenez n'a pas vraiment d'importance, si elle est théoriquement plus lente, l'utilisateur pourrait l'apprécier. ;) Donc non, StringBuilder n'est pas un poids lourd dans la plupart des situations.

11 votes

@Peter, non, ce n'est absolument pas pour être lu en temps réel par des humains ! Il est là pour aider à l'analyse lorsque les choses tournent mal. La sortie du journal sera typiquement des milliers de lignes par seconde, donc elle doit être efficace.

263voto

Itamar Points 1467

J'ai pris hhafez et ajouté un test de mémoire :

private static void test() {
    Runtime runtime = Runtime.getRuntime();
    long memory;
    ...
    memory = runtime.freeMemory();
    // for loop code
    memory = memory-runtime.freeMemory();

J'ai exécuté cette opération séparément pour chaque approche, l'opérateur '+', String.format et StringBuilder (appelant toString()), afin que la mémoire utilisée ne soit pas affectée par les autres approches. J'ai ajouté plus de concaténations, rendant la chaîne de caractères comme "Blah" + i + "Blah "+ i + "Blah" + i + "Blah".

Les résultats sont les suivants (moyenne de 5 passages chacun) :
Temps d'approche (ms) Mémoire allouée (long)
opérateur '+' 747 320,504
String.format 16484 373,312
StringBuilder 769 57 344

Nous pouvons voir que String '+' et StringBuilder sont pratiquement identiques en termes de temps, mais StringBuilder est beaucoup plus efficace en termes d'utilisation de la mémoire. Ceci est très important lorsque nous avons de nombreux appels de log (ou toute autre déclaration impliquant des chaînes) dans un intervalle de temps assez court pour que le Garbage Collector ne puisse pas nettoyer les nombreuses instances de chaînes résultant de l'opérateur '+'.

Et une note, BTW, n'oubliez pas de vérifier la journalisation niveau avant de construire le message.

Conclusions :

  1. Je vais continuer à utiliser StringBuilder.
  2. J'ai trop de temps ou trop peu de vie.

9 votes

"N'oubliez pas de vérifier le niveau de journalisation avant de construire le message", c'est un bon conseil, cela devrait être fait au moins pour les messages de débogage, car il pourrait y en avoir beaucoup et ils ne devraient pas être activés en production.

50 votes

Non, ce n'est pas bien. Désolé d'être brutal mais le nombre de votes positifs qu'il a attiré est tout simplement alarmant. En utilisant le + compile vers l'opérateur équivalent StringBuilder code. Les microbenchmarks comme celui-ci ne sont pas un bon moyen de mesurer les performances - pourquoi ne pas utiliser jvisualvm, il est dans le jdk pour une raison. String.format() sera est plus lent, mais en raison du temps nécessaire à l'analyse de la chaîne de format plutôt qu'à l'allocation d'objets. Report de la création des artefacts de journalisation jusqu'à ce que vous soyez sûr qu'ils sont nécessaires. est bon conseil, mais si cela a un impact sur les performances, c'est au mauvais endroit.

1 votes

@CurtainDog, votre commentaire a été fait sur un post vieux de quatre ans, pouvez-vous indiquer une documentation ou créer une réponse séparée pour aborder la différence ?

135voto

hhafez Points 13240

J'ai écrit une petite classe pour tester laquelle des deux a la meilleure performance et + arrive en tête du format. par un facteur de 5 à 6. Essayez vous-même

import java.io.*;
import java.util.Date;

public class StringTest{

    public static void main( String[] args ){
    int i = 0;
    long prev_time = System.currentTimeMillis();
    long time;

    for( i = 0; i< 100000; i++){
        String s = "Blah" + i + "Blah";
    }
    time = System.currentTimeMillis() - prev_time;

    System.out.println("Time after for loop " + time);

    prev_time = System.currentTimeMillis();
    for( i = 0; i<100000; i++){
        String s = String.format("Blah %d Blah", i);
    }
    time = System.currentTimeMillis() - prev_time;
    System.out.println("Time after for loop " + time);

    }
}

L'exécution de ce qui précède pour différents N montre que les deux se comportent de manière linéaire, mais String.format est 5 à 30 fois plus lent.

La raison en est que dans l'implémentation actuelle String.format analyse d'abord l'entrée avec des expressions régulières, puis remplit les paramètres. La concaténation avec plus, d'un autre côté, est optimisée par javac (pas par le JIT) et utilise StringBuilder.append directement.

Runtime comparison

14 votes

Ce test présente un défaut : il n'est pas tout à fait représentatif de l'ensemble du formatage des chaînes. Il y a souvent une logique impliquée dans ce qu'il faut inclure et une logique pour formater des valeurs spécifiques en chaînes de caractères. Tout test réel devrait porter sur des scénarios du monde réel.

0 votes

L'utilisation d'un StringBuffer (thread safe) ou d'un StringBuilder (plus rapide que le StringBuffer mais pas thread safe) ne serait-elle pas mieux que l'utilisation de la concaténation ("+") ?

9 votes

Il y avait une autre question sur SO à propos de + vers StringBuffer, dans les versions récentes de Java + a été remplacé par StringBuffer quand c'était possible afin que les performances ne soient pas différentes

21voto

Raphaël Points 1018

Votre vieux style moche est automatiquement compilé par JAVAC 1.6 en tant que :

StringBuilder sb = new StringBuilder("What do you get if you multiply ");
sb.append(varSix);
sb.append(" by ");
sb.append(varNine);
sb.append("?");
String s =  sb.toString();

Il n'y a donc absolument aucune différence entre cette méthode et l'utilisation d'un StringBuilder.

String.format est beaucoup plus lourd puisqu'il crée un nouveau formateur, analyse votre chaîne de format d'entrée, crée un StringBuilder, y ajoute tout et appelle toString().

0 votes

En termes de lisibilité, le code que vous avez posté est beaucoup plus... encombrant que String.format( "What do you get if you multiply %d by %d ?", varSix, varNine) ;

17 votes

Aucune différence entre + y StringBuilder en effet. Malheureusement, il y a beaucoup d'informations erronées dans les autres réponses de ce fil. Je suis presque tenté de changer la question en how should I not be measuring performance .

12voto

Dustin Getz Points 8514

Le format String.format de Java fonctionne comme suit :

  1. il analyse la chaîne de format, en la décomposant en une liste de morceaux de format.
  2. il itère les morceaux de format, en les rendant dans un StringBuilder, qui est essentiellement un tableau qui se redimensionne lui-même si nécessaire, en le copiant dans un nouveau tableau. c'est nécessaire parce que nous ne savons pas encore quelle taille allouer au String final
  3. StringBuilder.toString() copie son tampon interne dans une nouvelle chaîne.

si la destination finale de ces données est un flux (par exemple, le rendu d'une page web ou l'écriture dans un fichier), vous pouvez assembler les morceaux de format directement dans votre flux :

new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");

Je suppose que l'optimiseur supprimera le traitement de la chaîne de format. Si c'est le cas, vous vous retrouvez avec un équivalent amorti performance pour dérouler manuellement votre String.format en un StringBuilder.

5 votes

Je ne pense pas que votre spéculation sur l'optimisation du traitement de la chaîne de format soit correcte. Lors de tests réels effectués avec Java 7, j'ai constaté que l'utilisation de la fonction String.format dans des boucles internes (exécutées des millions de fois) a eu pour résultat que plus de 10 % de mon temps d'exécution a été passé en java.util.Formatter.parse(String) . Cela semble indiquer que dans les boucles internes, vous devriez éviter d'appeler Formatter.format ou tout ce qui l'appelle, y compris PrintStream.format (une faille dans la librairie standard de Java, IMO, d'autant plus que vous ne pouvez pas mettre en cache la chaîne de format analysée).

8voto

dw.mackie Points 1149

Pour développer/corriger la première réponse ci-dessus, ce n'est pas la traduction pour laquelle String.format serait utile, en fait.
String.format est utile lorsque vous imprimez une date/heure (ou un format numérique, etc.) et qu'il existe des différences de localisation (par exemple, certains pays imprimeront 04Feb2009 et d'autres Feb042009).
Avec la traduction, il s'agit simplement de déplacer toutes les chaînes externalisables (comme les messages d'erreur et autres) dans un faisceau de propriétés afin de pouvoir utiliser le bon faisceau pour la bonne langue, en utilisant ResourceBundle et MessageFormat.

Au vu de tout ce qui précède, je dirais que, du point de vue des performances, String.format par rapport à la concaténation simple se résume à ce que vous préférez. Si vous préférez regarder les appels à .format plutôt que la concaténation, alors par tous les moyens, allez-y.
Après tout, le code est beaucoup plus lu qu'il n'est écrit.

1 votes

Je dirais que les performances de String.format par rapport à la concaténation simple dépendent de ce que vous préférez. Je pense que c'est incorrect. En termes de performances, la concaténation est bien meilleure. Pour plus de détails, jetez un coup d'œil à ma réponse.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X