Concatenación de cadenas con Groovy

91

¿Cuál es la mejor forma (idiomática) de concatenar cadenas en Groovy?

Opción 1:

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

Opcion 2:

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

He encontrado un punto interesante sobre este tema en el antiguo sitio web de Groovy: Cosas que puedes hacer pero es mejor dejarlas sin hacer.

Como en Java, puede concatenar cadenas con el símbolo "+". Pero Java solo necesita que uno de los dos elementos de una expresión "+" sea una Cadena, sin importar si está en el primer lugar o en el último. Java usará el método toString () en el objeto que no es String de su expresión "+". Pero en Groovy, debería estar seguro de que el primer elemento de su expresión "+" implementa el método plus () de la manera correcta, porque Groovy lo buscará y lo usará. En Groovy GDK, solo las clases Number y String / StringBuffer / Character tienen implementado el método plus () para concatenar cadenas. Para evitar sorpresas, utilice siempre GStrings.

Arturo Herrero
fuente

Respuestas:

122

Siempre opto por el segundo método (usando la plantilla GString), aunque cuando hay más de un par de parámetros como el que tiene, tiendo a ajustarlos ${X}porque creo que los hace más legibles.

La ejecución de algunos puntos de referencia (utilizando el excelente módulo GBench de Nagai Masato ) en estos métodos también muestra que la creación de plantillas es más rápida que los otros métodos:

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

Eso me da el siguiente resultado en mi máquina:

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

Entonces, con la legibilidad y la velocidad a su favor, recomendaría usar plantillas ;-)

Nota: Si se agrega toString()al final de los métodos GString para que la salida escriba lo mismo que las otras métricas, y que sea una prueba más justa, StringBuildery StringBuffervencer a los métodos para GString velocidad. Sin embargo, como GString se puede usar en lugar de String para la mayoría de las cosas (solo debe tener cuidado con las claves de mapa y las declaraciones SQL), en su mayoría se puede dejar sin esta conversión final

Agregar estas pruebas (como se ha pedido en los comentarios)

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

Ahora obtenemos los resultados:

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

Entonces, como puede ver (como dije), es más lento que StringBuilder o StringBuffer, pero aún un poco más rápido que agregar Strings ...

Pero aún mucho más legible.

Editar después del comentario de ruralcoder a continuación

Actualizado a la última versión de gbench, cadenas más grandes para la concatenación y una prueba con un StringBuilder inicializado a un buen tamaño:

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

da

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
fuente
3
No estoy en desacuerdo con el uso de plantillas GString para la legibilidad, pero debería volver a ejecutar las pruebas con .toString()las dos pruebas GString adjuntas. Mi carrera muestra que luego se desempeñan casi igual que String adder. Supongo que la prueba que ha ejecutado en realidad no maneja la concatenación, por lo que solo crea un objeto GString y almacena las referencias. StringBuildersigue siendo el más rápido, sin duda, si lo necesita Stringen algún momento.
OverZealous
1
¡Me perdí la segunda mitad de eso de alguna manera! Por supuesto, incluso si deja el GString"como está", en algún momento debe convertirse en verdadero String(incluso para imprimirlo), por lo que el tiempo verdadero es el último conjunto. Al final, la legibilidad de las GStringplantillas supera StringBuildercuando el tiempo es tan cercano, por lo que es discutible. :-)
OverZealous
2
@OverZealous Ahhh sí, como siempre, hay mentiras, malditas mentiras y puntos de referencia ;-) La legibilidad es clave aquí, creo y como ya estamos usando Groovy, hemos dicho que el rendimiento bare-metal no es nuestra principal consideración; -)
tim_yates
1
Sí, una de las grandes ventajas de GStrings es que no se convierten en cadenas hasta el último momento. Lo que significa, por ejemplo, si registra un GString con un registrador como log4j por debajo del umbral de registro, el GString nunca se convierte en absoluto.
ataylor
1
Lo que falta en la prueba es StringBuilder con capacidad calculada. La razón es que foo + bar + baz provocará una o dos expansiones de búfer, lo que aumentará el tiempo.
ruralcoder
19
def my_string = "some string"
println "here: " + my_string 

No estoy muy seguro de por qué la respuesta anterior debe ir a puntos de referencia, búferes de cadenas, pruebas, etc.

Snowcrash
fuente
1
Vota a favor de la simplicidad. Solo necesito concatenar dos cadenas. lol
harperville
1

Reproduciendo la respuesta tim_yates en el hardware actual y agregando los métodos leftShift () y concat () para verificar el hallazgo:

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

El resultado muestra que concat () es la solución más rápida para una cadena pura, pero si puede manejar GString en otro lugar, la plantilla GString todavía está por delante, mientras que la mención de honor debe ir a leftShift () (operador bit a bit) y StringBuffer () con inicial asignación:

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
fuente