Concaténation de chaînes avec Groovy

91

Quelle est la meilleure façon (idiomatique) de concaténer des chaînes dans Groovy?

Option 1:

calculateAccountNumber(bank, branch, checkDigit, account) {
    bank + branch + checkDigit + account
}

Option 2:

calculateAccountNumber(bank, branch, checkDigit, account) {
    "$bank$branch$checkDigit$account"
}

J'ai trouvé un point intéressant sur ce sujet dans l'ancien site Web de Groovy: les choses que vous pouvez faire, mais que vous feriez mieux de ne pas les faire.

Comme en Java, vous pouvez concaténer des chaînes avec le symbole "+". Mais Java n'a besoin que de l'un des deux éléments d'une expression "+" pour être une chaîne, peu importe si c'est en premier lieu ou en dernier. Java utilisera la méthode toString () dans l'objet non-String de votre expression "+". Mais dans Groovy, vous devriez juste être sûr que le premier élément de votre expression "+" implémente la méthode plus () de la bonne manière, car Groovy la recherchera et l'utilisera. Dans Groovy GDK, seules les classes Number et String / StringBuffer / Character ont la méthode plus () implémentée pour concaténer des chaînes. Pour éviter les surprises, utilisez toujours GStrings.

Arturo Herrero
la source

Réponses:

122

Je choisis toujours la deuxième méthode (en utilisant le modèle GString), bien que quand il y a plus de quelques paramètres comme vous, j'ai tendance à les envelopper ${X}car je trouve que cela le rend plus lisible.

L'exécution de quelques benchmarks (en utilisant l' excellent module GBench de Nagai Masato ) sur ces méthodes montre également que la création de modèles est plus rapide que les autres méthodes:

@Grab( 'com.googlecode.gbench:gbench:0.3.0-groovy-2.0' )
import gbench.*

def (foo,bar,baz) = [ 'foo', 'bar', 'baz' ]
new BenchmarkBuilder().run( measureCpuTime:false ) {
  // Just add the strings
  'String adder' {
    foo + bar + baz
  }
  // Templating
  'GString template' {
    "$foo$bar$baz"
  }
  // I find this more readable
  'Readable GString template' {
    "${foo}${bar}${baz}"
  }
  // StringBuilder
  'StringBuilder' {
    new StringBuilder().append( foo )
                       .append( bar )
                       .append( baz )
                       .toString()
  }
  'StringBuffer' {
    new StringBuffer().append( foo )
                      .append( bar )
                      .append( baz )
                      .toString()
  }
}.prettyPrint()

Cela me donne la sortie suivante sur ma machine:

Environment
===========
* Groovy: 2.0.0
* JVM: Java HotSpot(TM) 64-Bit Server VM (20.6-b01-415, Apple Inc.)
    * JRE: 1.6.0_31
    * Total Memory: 81.0625 MB
    * Maximum Memory: 123.9375 MB
* OS: Mac OS X (10.6.8, x86_64) 

Options
=======
* Warm Up: Auto 
* CPU Time Measurement: Off

String adder               539
GString template           245
Readable GString template  244
StringBuilder              318
StringBuffer               370

Donc, avec la lisibilité et la vitesse en sa faveur, je recommanderais la création de modèles ;-)

NB: Si vous ajoutez toString()à la fin des méthodes GString pour rendre le type de sortie le même que les autres métriques, et en faire un test plus juste, StringBuilderet StringBufferbattez les méthodes GString pour la vitesse. Cependant, comme GString peut être utilisé à la place de String pour la plupart des choses (vous devez juste faire preuve de prudence avec les clés Map et les instructions SQL), il peut généralement être laissé sans cette conversion finale

Ajout de ces tests (comme cela a été demandé dans les commentaires)

  'GString template toString' {
    "$foo$bar$baz".toString()
  }
  'Readable GString template toString' {
    "${foo}${bar}${baz}".toString()
  }

Maintenant, nous obtenons les résultats:

String adder                        514
GString template                    267
Readable GString template           269
GString template toString           478
Readable GString template toString  480
StringBuilder                       321
StringBuffer                        369

Donc, comme vous pouvez le voir (comme je l'ai dit), c'est plus lent que StringBuilder ou StringBuffer, mais toujours un peu plus rapide que d'ajouter des chaînes ...

Mais encore beaucoup plus lisible.

Modifier après le commentaire de ruralcoder ci-dessous

Mise à jour vers le dernier gbench, des chaînes plus grandes pour la concaténation et un test avec un StringBuilder initialisé à une bonne taille:

@Grab( 'org.gperfutils:gbench:0.4.2-groovy-2.1' )

def (foo,bar,baz) = [ 'foo' * 50, 'bar' * 50, 'baz' * 50 ]
benchmark {
  // Just add the strings
  'String adder' {
    foo + bar + baz
  }
  // Templating
  'GString template' {
    "$foo$bar$baz"
  }
  // I find this more readable
  'Readable GString template' {
    "${foo}${bar}${baz}"
  }
  'GString template toString' {
    "$foo$bar$baz".toString()
  }
  'Readable GString template toString' {
    "${foo}${bar}${baz}".toString()
  }
  // StringBuilder
  'StringBuilder' {
    new StringBuilder().append( foo )
                       .append( bar )
                       .append( baz )
                       .toString()
  }
  'StringBuffer' {
    new StringBuffer().append( foo )
                      .append( bar )
                      .append( baz )
                      .toString()
  }
  'StringBuffer with Allocation' {
    new StringBuffer( 512 ).append( foo )
                      .append( bar )
                      .append( baz )
                      .toString()
  }
}.prettyPrint()

donne

Environment
===========
* Groovy: 2.1.6
* JVM: Java HotSpot(TM) 64-Bit Server VM (23.21-b01, Oracle Corporation)
    * JRE: 1.7.0_21
    * Total Memory: 467.375 MB
    * Maximum Memory: 1077.375 MB
* OS: Mac OS X (10.8.4, x86_64)

Options
=======
* Warm Up: Auto (- 60 sec)
* CPU Time Measurement: On

                                    user  system  cpu  real

String adder                         630       0  630   647
GString template                      29       0   29    31
Readable GString template             32       0   32    33
GString template toString            429       0  429   443
Readable GString template toString   428       1  429   441
StringBuilder                        383       1  384   396
StringBuffer                         395       1  396   409
StringBuffer with Allocation         277       0  277   286
tim_yates
la source
3
Je ne suis pas en désaccord avec l'utilisation des modèles GString pour la lisibilité, mais vous devriez réexécuter les tests .toString()en ajoutant aux deux tests GString. Ma course montre qu'ils exécutent alors presque la même chose que String adder. Je suppose que le test que vous avez exécuté ne gère pas réellement la concaténation, il s'agit donc simplement de créer un objet GString et de stocker les références. StringBuilderest toujours le plus rapide, haut la main, si vous en avez besoin Stringà un moment donné.
OverZealous
1
J'ai raté la seconde moitié de ça! Bien sûr, même si vous laissez le GString"tel quel ", à un moment donné il doit être converti en un vrai String, (même juste pour l'imprimer), donc le vrai timing est le dernier ensemble. En fin de compte, la lisibilité des GStringmodèles bat StringBuilderlorsque le timing est si proche, donc c'est sans objet. :-)
OverZealous
2
@OverZealous Ahhh oui, comme toujours, il y a des mensonges, des mensonges damnés et des repères ;-) La lisibilité est la clé ici je pense et comme nous utilisons déjà Groovy, nous avons déclaré que la performance bare-metal n'est pas notre principale considération; -)
tim_yates
1
Oui, l'un des grands avantages des GStrings est qu'ils ne sont convertis en chaînes qu'au dernier moment. Ce qui signifie, par exemple, si vous enregistrez une GString avec un enregistreur comme log4j en dessous du seuil de journalisation, la GString n'est jamais convertie du tout.
ataylor
1
Ce qui manque au test, c'est StringBuilder avec une capacité calculée. La raison est que foo + bar + baz provoquera une ou deux expansions de tampon, ce qui ajoute du temps.
ruralcoder
19
def my_string = "some string"
println "here: " + my_string 

Je ne sais pas trop pourquoi la réponse ci-dessus doit aller dans les benchmarks, les tampons de chaînes, les tests, etc.

Snowcrash
la source
1
Upvote pour la simplicité. J'ai juste besoin de concaténer deux chaînes. lol
harperville le
1

Reproduction de la réponse tim_yates sur le matériel actuel et ajout des méthodes leftShift () et concat () pour vérifier la conclusion:

  'String leftShift' {
    foo << bar << baz
  }
  'String concat' {
    foo.concat(bar)
       .concat(baz)
       .toString()
  }

Le résultat montre que concat () est la solution la plus rapide pour une chaîne pure, mais si vous pouvez gérer GString ailleurs, le modèle GString est toujours en avance, tandis que la mention honorable doit aller à leftShift () (opérateur au niveau du bit) et StringBuffer () avec l'initiale allocation:

Environment
===========
* Groovy: 2.4.8
* JVM: OpenJDK 64-Bit Server VM (25.191-b12, Oracle Corporation)
    * JRE: 1.8.0_191
    * Total Memory: 238 MB
    * Maximum Memory: 3504 MB
* OS: Linux (4.19.13-300.fc29.x86_64, amd64)

Options
=======
* Warm Up: Auto (- 60 sec)
* CPU Time Measurement: On

                                    user  system  cpu  real

String adder                         453       7  460   469
String leftShift                     287       2  289   295
String concat                        169       1  170   173
GString template                      24       0   24    24
Readable GString template             32       0   32    32
GString template toString            400       0  400   406
Readable GString template toString   412       0  412   419
StringBuilder                        325       3  328   334
StringBuffer                         390       1  391   398
StringBuffer with Allocation         259       1  260   265
thoroc
la source