Concatenazione di stringhe con Groovy


91

Qual è il modo migliore (idiomatico) per concatenare gli archi in Groovy?

Opzione 1:

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

Opzione 2:

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

Ho trovato un punto interessante su questo argomento nel vecchio sito Web di Groovy: cose che puoi fare ma è meglio lasciare incompiute.

Come in Java, puoi concatenare le stringhe con il simbolo "+". Ma Java ha bisogno solo che uno dei due elementi di un'espressione "+" sia una stringa, non importa se si trova al primo o all'ultimo posto. Java utilizzerà il metodo toString () nell'oggetto non String della tua espressione "+". Ma in Groovy, dovresti essere sicuro che il primo elemento della tua espressione "+" implementa il metodo plus () nel modo giusto, perché Groovy lo cercherà e lo userà. In Groovy GDK, solo le classi Number e String / StringBuffer / Character hanno il metodo plus () implementato per concatenare le stringhe. Per evitare sorprese, usa sempre GStrings.

Risposte:


122

Vado sempre per il secondo metodo (usando il modello GString), anche se quando ci sono più di un paio di parametri come te, tendo a includerli ${X} perché trovo che lo renda più leggibile.

L'esecuzione di alcuni benchmark (utilizzando l' eccellente modulo GBench di Nagai Masato ) su questi metodi mostra anche che il templating è più veloce degli altri metodi:

@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()

Questo mi dà il seguente output sulla mia macchina:

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

Quindi, con la leggibilità e la velocità a suo favore, consiglierei di creare modelli ;-)

NB: se aggiungi toString()alla fine dei metodi GString per rendere il tipo di output uguale alle altre metriche e renderlo un test più equo, StringBuildereStringBuffer rendilo batti i metodi GString per la velocità. Tuttavia, poiché GString può essere utilizzato al posto di String per la maggior parte delle cose (devi solo prestare attenzione con le chiavi Map e le istruzioni SQL), può essere lasciato senza questa conversione finale

Aggiungendo questi test (come è stato chiesto nei commenti)

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

Ora otteniamo i risultati:

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

Quindi, come puoi vedere (come ho detto), è più lento di StringBuilder o StringBuffer, ma comunque un po 'più veloce dell'aggiunta di stringhe ...

Ma ancora molto più leggibile.

Modifica dopo il commento di ruralcoder di seguito

Aggiornato all'ultimo gbench, stringhe più grandi per la concatenazione e un test con uno StringBuilder inizializzato a una buona dimensione:

@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()

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

3
Non sono in disaccordo con l'utilizzo dei modelli GString per la leggibilità, ma dovresti rieseguire i test con l' .toString()aggiunta dei due test GString. La mia corsa mostra che poi si comportano quasi allo stesso modo di String adder. La mia ipotesi è che il test che hai eseguito in realtà non gestisca la concatenazione, quindi si limita a creare un oggetto GString e memorizzare i riferimenti. StringBuilderè ancora il più veloce, senza dubbio, se ad un Stringcerto punto ne hai bisogno .
OverZealous

1
In qualche modo mi sono perso la seconda metà! Ovviamente, anche se lasci GString"così com'è", a un certo punto deve essere convertito in vero String(anche solo per stamparlo), quindi il vero tempismo è l'ultimo set. Alla fine la leggibilità diGString modelli batte StringBuilderquando il tempismo è così vicino, quindi è discutibile. :-)
OverZealous

2
@OverZealous Ahhh sì, come sempre, ci sono bugie, dannate bugie e benchmark ;-) La leggibilità è la chiave qui, credo e poiché stiamo già usando Groovy, abbiamo affermato che le prestazioni bare-metal non sono la nostra principale considerazione; -)
tim_yates

1
Sì, uno dei grandi vantaggi di GStrings è che non vengono convertiti in stringhe fino all'ultimo momento. Il che significa, ad esempio, se si registra un GString con un logger come log4j al di sotto della soglia di registrazione, il GString non viene mai convertito.
ataylor

1
Ciò che manca al test è StringBuilder con capacità calcolata. Il motivo è che foo + bar + baz causerà una o due espansioni del buffer che si aggiungono al tempo.
ruralcoder

19
def my_string = "some string"
println "here: " + my_string 

Non sono abbastanza sicuro del motivo per cui la risposta sopra deve entrare in benchmark, buffer di stringhe, test, ecc.


1
Upvote per semplicità. Ho solo bisogno di concatenare due stringhe. lol
harperville

1

Riprodurre la risposta tim_yates sull'hardware corrente e aggiungere il metodo leftShift () e concat () per verificare il risultato:

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

Il risultato mostra che concat () è la soluzione più veloce per una stringa pura, ma se puoi gestire GString da qualche altra parte, il modello GString è ancora avanti, mentre una menzione d'onore dovrebbe andare a leftShift () (operatore bit a bit) e StringBuffer () con l'iniziale assegnazione:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.