Est-il préférable d'utiliser String.format sur la concaténation de chaînes en Java?


273

Existe-t-il une différence perceptible entre l'utilisation String.formatet la concaténation de chaînes en Java?

J'ai tendance à utiliser String.formatmais parfois je glisse et j'utilise une concaténation. Je me demandais si l'un était meilleur que l'autre.

La façon dont je le vois, String.formatvous donne plus de pouvoir dans le "formatage" de la chaîne; et la concaténation signifie que vous n'avez pas à vous soucier de mettre accidentellement un% s supplémentaire ou d'en manquer un.

String.format est également plus court.

Laquelle est la plus lisible dépend du fonctionnement de votre tête.


Je pense que nous pouvons aller avec MessageFormat.format. Veuillez consulter la réponse stackoverflow.com/a/56377112/1491414 pour plus d'informations.
Ganesa Vijayakumar

Réponses:


242

Je dirais que c'est une meilleure pratique à utiliser String.format(). La raison principale est qu'il String.format()peut être plus facilement localisé avec du texte chargé à partir de fichiers de ressources alors que la concaténation ne peut pas être localisée sans produire un nouvel exécutable avec un code différent pour chaque langue.

Si vous prévoyez que votre application soit localisable, vous devriez également prendre l'habitude de spécifier des positions d'argument pour vos jetons de format:

"Hello %1$s the time is %2$t"

Cela peut ensuite être localisé et faire échanger les jetons de nom et d'heure sans nécessiter une recompilation de l'exécutable pour tenir compte des différents ordres. Avec les positions d'argument, vous pouvez également réutiliser le même argument sans le passer deux fois dans la fonction:

String.format("Hello %1$s, your name is %1$s and the time is %2$t", name, time)

1
Pouvez-vous m'indiquer une documentation qui explique comment travailler avec les positions / ordre des arguments en Java (c'est-à-dire comment référencer les arguments par leur position)? Merci.
markvgti

13
Mieux vaut tard que jamais, version Java aléatoire: docs.oracle.com/javase/1.5.0/docs/api/java/util/…
Aksel

174

À propos des performances:

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }
  long end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;

  start = System.currentTimeMillis();
  for(int i = 0; i < 1000000; i++){
    String s = String.format("Hi %s; Hi to you %s",i, + i*2);
  }
  end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
}

Les résultats temporels sont les suivants:

  • Concaténation = 265 millisecondes
  • Format = 4141 milliseconde

Par conséquent, la concaténation est beaucoup plus rapide que String.format.


15
Ce sont toutes de mauvaises pratiques. Utilisez StringBuilder.
Amir Raminfar

8
StringBuilder est hors de portée ici (la question OP concernait la comparaison de String.format par rapport à la concaténation de chaînes) mais avez-vous effectué des données sur String Builder?
Icaro

108
@AmirRaminar: Le compilateur convertit automatiquement "+" en appels à StringBuilder.
Martin Schröder

40
@ MartinSchröder: Si vous exécutez, javap -c StringTest.classvous verrez que le compilateur convertit automatiquement "+" en StringBuilder uniquement si vous n'êtes pas dans une boucle. Si la concaténation est entièrement effectuée sur une seule ligne, c'est la même chose que d'utiliser '+', mais si vous utilisez myString += "morechars";ou myString += anotherString;sur plusieurs lignes, vous remarquerez que plusieurs StringBuilder peuvent être créés, donc l'utilisation de "+" n'est pas toujours aussi efficace comme StringBuilder.
ccpizza

7
@Joffrey: ce que je voulais dire, c'est que les boucles for +ne sont pas converties en StringBuilder.append()mais un new StringBuilder()se produit à chaque itération.
ccpizza

39

Puisqu'il y a une discussion sur les performances, j'ai pensé que j'ajouterais une comparaison qui incluait StringBuilder. Il est en fait plus rapide que le concat et, naturellement, l'option String.format.

Pour en faire une sorte de comparaison de pommes à pommes, j'instancie un nouveau StringBuilder dans la boucle plutôt qu'à l'extérieur (c'est en fait plus rapide que de faire une seule instanciation très probablement en raison de la surcharge de réallocation d'espace pour l'ajout en boucle à la fin de un constructeur).

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    log.info("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    log.info("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("; Hi to you ").append(i * 2);
    }

    end = System.currentTimeMillis();

    log.info("String Builder = " + ((end - start)) + " millisecond");
  • 2012-01-11 16: 30: 46,058 INFO [TestMain] - Format = 1416 millisecondes
  • 2012-01-11 16: 30: 46,190 INFO [TestMain] - Concaténation = 134 millisecondes
  • 2012-01-11 16: 30: 46,313 INFO [TestMain] - String Builder = 117 millisecondes

21
Le test StringBuilder n'appelle pas toString (), donc ce n'est pas une comparaison juste. Je soupçonne que vous trouverez que c'est dans l'erreur de mesure des performances de concaténation si vous corrigez ce bogue.
Jamey Sharp

15
Dans les tests de concaténation et de format, vous avez demandé a String. Pour être juste, le test StringBuilder a besoin d'une étape finale qui transforme le contenu de StringBuilder en une chaîne. Vous faites cela en appelant bldString.toString(). J'éspère que ça l'explique?
Jamey Sharp

4
Jamey Sharp a parfaitement raison. L'invocation de bldString.toString () est à peu près la même sinon plus lente que la concaténation de chaînes.
Akos Cz

3
String s = bldString.toString(); Les synchronisations étaient avec concaténation et constructeur de cordes presque à égalité: Format = 1520 millisecond , Concatenation = 167 millisecond, String Builder = 173 millisecond je les ai couru dans une boucle et moyennées chacun dehors pour obtenir un bon représentant: (optimisation pré-jvm, va essayer une boucle 10000+ quand je reçois le temps)
TechTrip

3
Comment savez-vous même si le code est exécuté? Les variables ne sont jamais lues ou utilisées, vous ne pouvez pas être sûr que JIT ne supprime pas ce code en premier lieu.
alobodzk

37

Un problème .formatest que vous perdez la sécurité de type statique. Vous pouvez avoir trop peu d'arguments pour votre format, et vous pouvez avoir les mauvais types pour les spécificateurs de format - les deux conduisant à une IllegalFormatException exécution , vous pourriez donc vous retrouver avec un code de journalisation qui interrompt la production.

En revanche, les arguments de +peuvent être testés par le compilateur.

L' histoire de la sécurité de(sur laquelle la formatfonction est modelée) est longue et effrayante.


16
juste pour mémoire, les IDE modernes (par exemple IntelliJ) aident à compter les arguments et à faire correspondre les types
Ron Klein

2
Bon point sur la compilation, je vous recommande de faire ces vérifications via FindBugs (qui peut s'exécuter dans l'IDE ou via Maven pendant la construction), notez que cela vérifiera également la mise en forme dans tous vos journaux! Cela fonctionne quels que soient les utilisateurs IDE
Christophe Roussy

20

Laquelle est la plus lisible dépend du fonctionnement de votre tête.

Vous avez obtenu votre réponse juste là.

C'est une question de goût personnel.

La concaténation de chaînes est légèrement plus rapide, je suppose, mais cela devrait être négligeable.


3
Je suis d'accord. Penser aux différences de performances ici n'est principalement qu'une optimisation prématurée - dans le cas peu probable où le profilage montrerait qu'il y a un problème ici, alors inquiétez-vous.
Jonik

3
Ce n'est vraiment une question de goût personnel que si le projet est petit et n'a jamais été destiné à être internationalisé de manière significative. Sinon, String.format l'emporte sur la concaténation dans tous les sens.
workmad3

4
Je ne suis pas d'accord. Quelle que soit la taille du projet, vous allez à peine localiser chaque chaîne qui y est jamais construite. En d'autres termes, cela dépend de la situation (à quoi servent les chaînes).
Jonik

Je ne peux pas imaginer comment quelqu'un considérerait jamais 'String.format ("% s% s", a, b)' comme plus lisible que 'a + b', et compte tenu de la différence de vitesse de l'ordre de grandeur qui la réponse me semble claire (dans des situations qui ne nécessiteront pas de localisation telles que le débogage ou la plupart des instructions de journalisation).
BobDoolittle

16

Voici un test avec plusieurs tailles d'échantillon en millisecondes.

public class Time {

public static String sysFile = "/sys/class/camera/rear/rear_flash";
public static String cmdString = "echo %s > " + sysFile;

public static void main(String[] args) {

  int i = 1;
  for(int run=1; run <= 12; run++){
      for(int test =1; test <= 2 ; test++){
        System.out.println(
                String.format("\nTEST: %s, RUN: %s, Iterations: %s",run,test,i));
        test(run, i);
      }
      System.out.println("\n____________________________");
      i = i*3;
  }
}

public static void test(int run, int iterations){

      long start = System.nanoTime();
      for( int i=0;i<iterations; i++){
          String s = "echo " + i + " > "+ sysFile;
      }
      long t = System.nanoTime() - start;   
      String r = String.format("  %-13s =%10d %s", "Concatenation",t,"nanosecond");
      System.out.println(r) ;


     start = System.nanoTime();       
     for( int i=0;i<iterations; i++){
         String s =  String.format(cmdString, i);
     }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "Format",t,"nanosecond");
     System.out.println(r);

      start = System.nanoTime();          
      for( int i=0;i<iterations; i++){
          StringBuilder b = new StringBuilder("echo ");
          b.append(i).append(" > ").append(sysFile);
          String s = b.toString();
      }
     t = System.nanoTime() - start; 
     r = String.format("  %-13s =%10d %s", "StringBuilder",t,"nanosecond");
     System.out.println(r);
}

}

TEST: 1, RUN: 1, Iterations: 1
  Concatenation =     14911 nanosecond
  Format        =     45026 nanosecond
  StringBuilder =      3509 nanosecond

TEST: 1, RUN: 2, Iterations: 1
  Concatenation =      3509 nanosecond
  Format        =     38594 nanosecond
  StringBuilder =      3509 nanosecond

____________________________

TEST: 2, RUN: 1, Iterations: 3
  Concatenation =      8479 nanosecond
  Format        =     94438 nanosecond
  StringBuilder =      5263 nanosecond

TEST: 2, RUN: 2, Iterations: 3
  Concatenation =      4970 nanosecond
  Format        =     92976 nanosecond
  StringBuilder =      5848 nanosecond

____________________________

TEST: 3, RUN: 1, Iterations: 9
  Concatenation =     11403 nanosecond
  Format        =    287115 nanosecond
  StringBuilder =     14326 nanosecond

TEST: 3, RUN: 2, Iterations: 9
  Concatenation =     12280 nanosecond
  Format        =    209051 nanosecond
  StringBuilder =     11818 nanosecond

____________________________

TEST: 5, RUN: 1, Iterations: 81
  Concatenation =     54383 nanosecond
  Format        =   1503113 nanosecond
  StringBuilder =     40056 nanosecond

TEST: 5, RUN: 2, Iterations: 81
  Concatenation =     44149 nanosecond
  Format        =   1264241 nanosecond
  StringBuilder =     34208 nanosecond

____________________________

TEST: 6, RUN: 1, Iterations: 243
  Concatenation =     76018 nanosecond
  Format        =   3210891 nanosecond
  StringBuilder =     76603 nanosecond

TEST: 6, RUN: 2, Iterations: 243
  Concatenation =     91222 nanosecond
  Format        =   2716773 nanosecond
  StringBuilder =     73972 nanosecond

____________________________

TEST: 8, RUN: 1, Iterations: 2187
  Concatenation =    527450 nanosecond
  Format        =  10291108 nanosecond
  StringBuilder =    885027 nanosecond

TEST: 8, RUN: 2, Iterations: 2187
  Concatenation =    526865 nanosecond
  Format        =   6294307 nanosecond
  StringBuilder =    591773 nanosecond

____________________________

TEST: 10, RUN: 1, Iterations: 19683
  Concatenation =   4592961 nanosecond
  Format        =  60114307 nanosecond
  StringBuilder =   2129387 nanosecond

TEST: 10, RUN: 2, Iterations: 19683
  Concatenation =   1850166 nanosecond
  Format        =  35940524 nanosecond
  StringBuilder =   1885544 nanosecond

  ____________________________

TEST: 12, RUN: 1, Iterations: 177147
  Concatenation =  26847286 nanosecond
  Format        = 126332877 nanosecond
  StringBuilder =  17578914 nanosecond

TEST: 12, RUN: 2, Iterations: 177147
  Concatenation =  24405056 nanosecond
  Format        = 129707207 nanosecond
  StringBuilder =  12253840 nanosecond

1
StringBuilder est absolument la méthode la plus rapide lorsque vous ajoutez des caractères dans une boucle, par exemple, lorsque vous souhaitez créer une chaîne avec mille 1 en les ajoutant un par un. Voici plus d'informations: pellegrino.link/2015/08/22/…
Carlos Hoyos

J'aime la façon dont vous utilisez toujours String.format pour la sortie: D donc il y a un avantage. et pour être honnête si nous ne parlons pas de millions d'itérations, je préfère string.format pour la lisibilité car votre code montre l'avantage évident!
mohamnag

9

Voici le même test que ci-dessus avec la modification de l'appel de la méthode toString () sur le StringBuilder . Les résultats ci-dessous montrent que l'approche StringBuilder est juste un peu plus lente que la concaténation de chaînes en utilisant l' opérateur + .

fichier: StringTest.java

class StringTest {

  public static void main(String[] args) {

    String formatString = "Hi %s; Hi to you %s";

    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
        String s = String.format(formatString, i, +i * 2);
    }

    long end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        String s = "Hi " + i + "; Hi to you " + i * 2;
    }

    end = System.currentTimeMillis();

    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();

    for (int i = 0; i < 1000000; i++) {
        StringBuilder bldString = new StringBuilder("Hi ");
        bldString.append(i).append("Hi to you ").append(i * 2).toString();
    }

    end = System.currentTimeMillis();

    System.out.println("String Builder = " + ((end - start)) + " millisecond");

  }
}

Commandes Shell: (compiler et exécuter StringTest 5 fois)

> javac StringTest.java
> sh -c "for i in \$(seq 1 5); do echo \"Run \${i}\"; java StringTest; done"

Résultats :

Run 1
Format = 1290 millisecond
Concatenation = 115 millisecond
String Builder = 130 millisecond

Run 2
Format = 1265 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond

Run 3
Format = 1303 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 4
Format = 1297 millisecond
Concatenation = 114 millisecond
String Builder = 127 millisecond

Run 5
Format = 1270 millisecond
Concatenation = 114 millisecond
String Builder = 126 millisecond

6

String.format()est plus qu'une simple concaténation de chaînes. Par exemple, vous pouvez afficher des nombres dans des paramètres régionaux spécifiques à l'aide de String.format().

Cependant, si vous ne vous souciez pas de la localisation, il n'y a pas de différence fonctionnelle. Peut-être que l'un est plus rapide que l'autre, mais dans la plupart des cas, il sera négligeable.


4

En règle générale, la concaténation de chaînes doit être préférée à String.format. Ce dernier présente deux principaux inconvénients:

  1. Il n'encode pas la chaîne à construire de manière locale.
  2. Le processus de construction est codé dans une chaîne.

Par point 1, je veux dire qu'il n'est pas possible de comprendre ce que fait un String.format()appel en une seule passe séquentielle. On est obligé d'aller et venir entre la chaîne de format et les arguments, tout en comptant la position des arguments. Pour les concaténations courtes, ce n'est pas vraiment un problème. Dans ces cas, cependant, la concaténation de chaînes est moins détaillée.

Par point 2, je veux dire que la partie importante du processus de construction est codée dans la chaîne de format (en utilisant un DSL). L'utilisation de chaînes pour représenter le code présente de nombreux inconvénients. Il n'est pas intrinsèquement sûr pour le type et complique la mise en évidence de la syntaxe, l'analyse de code, l'optimisation, etc.

Bien entendu, lors de l'utilisation d'outils ou de frameworks externes au langage Java, de nouveaux facteurs peuvent entrer en jeu.


2

Je n'ai pas fait de benchmarks spécifiques, mais je pense que la concaténation peut être plus rapide. String.format () crée un nouveau formateur qui, à son tour, crée un nouveau StringBuilder (avec une taille de seulement 16 caractères). C'est une bonne quantité de surcharge, surtout si vous formatez une chaîne plus longue et que StringBuilder doit continuer à être redimensionné.

Cependant, la concaténation est moins utile et plus difficile à lire. Comme toujours, cela vaut la peine de faire un benchmark sur votre code pour voir lequel est le meilleur. Les différences peuvent être négligeables dans l'application serveur une fois que vos ensembles de ressources, paramètres régionaux, etc. sont chargés en mémoire et que le code est JITted.

Peut-être que comme meilleure pratique, ce serait une bonne idée de créer votre propre Formateur avec un StringBuilder (Appendable) et des Paramètres régionaux correctement dimensionnés et de les utiliser si vous avez beaucoup de mise en forme à faire.


2

Il pourrait y avoir une différence perceptible.

String.format est assez complexe et utilise une expression régulière en dessous, alors ne prenez pas l'habitude de l'utiliser partout, mais seulement là où vous en avez besoin.

StringBuilder serait un ordre de grandeur plus rapide (comme quelqu'un l'a déjà souligné ici).


1

Je pense que nous pouvons aller de l'avant MessageFormat.formatcar il devrait être bon à la fois en termes de lisibilité et de performances.

J'ai utilisé le même programme que celui utilisé par Icaro dans sa réponse ci-dessus et je l'ai amélioré avec un code ajouté MessageFormatpour expliquer les performances.

  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = "Hi " + i + "; Hi to you " + i * 2;
    }
    long end = System.currentTimeMillis();
    System.out.println("Concatenation = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = String.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("Format = " + ((end - start)) + " millisecond");

    start = System.currentTimeMillis();
    for (int i = 0; i < 1000000; i++) {
      String s = MessageFormat.format("Hi %s; Hi to you %s", i, +i * 2);
    }
    end = System.currentTimeMillis();
    System.out.println("MessageFormat = " + ((end - start)) + " millisecond");
  }

Concaténation = 69 millisecondes

Format = 1435 millisecondes

MessageFormat = 200 millisecondes

MISES À JOUR:

Selon le rapport SonarLint, les chaînes de format de style Printf doivent être utilisées correctement (squid: S3457)

Étant donné que les printf-stylechaînes de format sont interprétées au moment de l'exécution plutôt que validées par le compilateur, elles peuvent contenir des erreurs entraînant la création de chaînes incorrectes. Cette règle valide statiquement la corrélation des printf-stylechaînes de format à leurs arguments lors de l' appel au format (...) les méthodes java.util.Formatter, java.lang.String, java.io.PrintStream, MessageFormat, et les java.io.PrintWriterclasses et les printf(...)méthodes de java.io.PrintStreamou java.io.PrintWriterclasses.

Je remplace le style printf par les accolades et j'ai obtenu des résultats intéressants comme ci-dessous.

Concaténation = 69 millisecondes
Format = 1107 millisecondes
Format: accolades = 416 millisecondes
MessageFormat = 215 millisecondes
MessageFormat: accolades = 2517 millisecondes

Ma conclusion:
Comme je l'ai souligné ci-dessus, l'utilisation de String.format avec des accolades devrait être un bon choix pour obtenir des avantages de bonne lisibilité et de performances.


0

Vous ne pouvez pas comparer la concaténation de chaînes et String.Format par le programme ci-dessus.

Vous pouvez également essayer de changer la position d'utilisation de votre String.Format et Concatenation dans votre bloc de code comme ci-dessous

public static void main(String[] args) throws Exception {      
  long start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = String.format( "Hi %s; Hi to you %s",i, + i*2);
  }

  long end = System.currentTimeMillis();
  System.out.println("Format = " + ((end - start)) + " millisecond");
  start = System.currentTimeMillis();

  for( int i=0;i<1000000; i++){
    String s = "Hi " + i + "; Hi to you " + i*2;
  }

  end = System.currentTimeMillis();
  System.out.println("Concatenation = " + ((end - start)) + " millisecond") ;
}

Vous serez surpris de voir que le format fonctionne plus rapidement ici. En effet, les objets initiaux créés peuvent ne pas être libérés et il peut y avoir un problème d'allocation de mémoire et donc de performances.


3
avez-vous essayé votre code? La concaténation est toujours dix fois plus rapide
Icaro

Qu'en est-il des millis pris pour exécuter ce "System.currentTimeMillis ()": P.
rehan

0

Il faut un peu de temps pour s'habituer à String.Format, mais cela en vaut la peine dans la plupart des cas. Dans le monde de NRA (ne jamais répéter quoi que ce soit), il est extrêmement utile de conserver vos messages à jetons (journalisation ou utilisateur) dans une bibliothèque constante (je préfère ce qui équivaut à une classe statique) et de les appeler si nécessaire avec String.Format, que vous localisent ou non. Essayer d'utiliser une telle bibliothèque avec une méthode de concaténation est plus difficile à lire, à dépanner, à relire et à gérer avec toute approche nécessitant une concaténation. Le remplacement est une option, mais je doute qu'il soit performant. Après des années d'utilisation, mon plus gros problème avec String.Format est que la longueur de l'appel est trop longue lorsque je le passe dans une autre fonction (comme Msg), mais il est facile de se déplacer avec une fonction personnalisée pour servir d'alias .

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.