String-Verkettung mit Groovy

90

Was ist der beste (idiomatische) Weg, um Strings in Groovy zu verketten?

Option 1:

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

Option 2:

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

Ich habe auf der alten Groovy-Website einen interessanten Punkt zu diesem Thema gefunden: Dinge, die Sie tun können, aber besser nicht tun.

Wie in Java können Sie Strings mit dem Symbol "+" verketten. Java benötigt jedoch nur eines der beiden Elemente eines "+" - Ausdrucks, um ein String zu sein, unabhängig davon, ob es sich um den ersten oder den letzten handelt. Java verwendet die toString () -Methode im Nicht-String-Objekt Ihres "+" - Ausdrucks. In Groovy sollten Sie jedoch sicher sein, dass das erste Element Ihres "+" - Ausdrucks die plus () -Methode richtig implementiert, da Groovy sie sucht und verwendet. In Groovy GDK ist nur für die Klassen Number und String / StringBuffer / Character die Methode plus () implementiert, um Zeichenfolgen zu verketten. Verwenden Sie immer GStrings, um Überraschungen zu vermeiden.

Arturo Herrero
quelle

Antworten:

122

Ich entscheide mich immer für die zweite Methode (unter Verwendung der GString-Vorlage). Wenn jedoch mehr als ein paar Parameter wie Sie vorhanden sind, neige ich dazu, sie einzuschließen, ${X}da ich finde, dass sie besser lesbar sind.

Das Ausführen einiger Benchmarks (unter Verwendung des hervorragenden GBench-Moduls von Nagai Masato ) für diese Methoden zeigt auch, dass das Templating schneller ist als die anderen Methoden:

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

Das gibt mir die folgende Ausgabe auf meinem Computer:

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

Mit Lesbarkeit und Geschwindigkeit würde ich empfehlen, Vorlagen zu erstellen ;-)

NB: Wenn Sie toString()am Ende der GString-Methoden hinzufügen, um den Ausgabetyp mit den anderen Metriken identisch zu machen, einen faireren Test durchzuführen StringBuilderund StringBufferdie GString-Methoden hinsichtlich der Geschwindigkeit zu übertreffen. Da GString für die meisten Dinge anstelle von String verwendet werden kann (Sie müssen nur mit Map-Schlüsseln und SQL-Anweisungen vorsichtig sein), kann es meistens ohne diese endgültige Konvertierung belassen werden

Hinzufügen dieser Tests (wie in den Kommentaren gefragt)

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

Jetzt bekommen wir die Ergebnisse:

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

Wie Sie sehen können (wie gesagt), ist es langsamer als StringBuilder oder StringBuffer, aber immer noch etwas schneller als das Hinzufügen von Strings ...

Aber noch viel lesbarer.

Bearbeiten Sie nach Kommentar von Ruralcoder unten

Auf die neueste Gbench aktualisiert, größere Zeichenfolgen zur Verkettung und ein Test mit einem StringBuilder, der auf eine gute Größe initialisiert wurde:

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

gibt

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
quelle
3
Ich bin mit der Verwendung von GString-Vorlagen aus Gründen der Lesbarkeit nicht einverstanden, aber Sie sollten die Tests mit .toString()den beiden GString-Tests angehängten erneut ausführen. Mein Lauf zeigt, dass sie dann fast die gleiche Leistung erbringen wie String adder. Ich vermute, dass der Test, den Sie ausgeführt haben, die Verkettung nicht wirklich handhabt, sondern lediglich ein GString-Objekt erstellt und die Referenzen speichert. StringBuilderist immer noch die schnellste, zweifellos, wenn Sie Stringirgendwann eine brauchen .
OverZealous
1
Ich habe die zweite Hälfte irgendwie verpasst! Natürlich, auch wenn Sie die verlassen GString„wie sie ist“, an einem gewissen Punkt ist es zu einem echten umgewandelt werden muss String, (auch nur zum Ausdrucken), so dass der wahre Zeitpunkt der letzte Satz ist. Am Ende GStringschlägt die Lesbarkeit von Vorlagen, StringBuilderwenn das Timing so nah ist, also ist es strittig. :-)
OverZealous
2
@OverZealous Ahhh ja, wie immer gibt es Lügen, verdammte Lügen und Benchmarks ;-) Die Lesbarkeit ist hier meiner Meinung nach der Schlüssel und da wir bereits Groovy verwenden, haben wir festgestellt, dass die Bare-Metal-Leistung nicht unsere Hauptüberlegung ist; -)
tim_yates
1
Ja, einer der großen Vorteile von GStrings ist, dass sie erst im letzten Moment in Strings umgewandelt werden. Wenn Sie beispielsweise einen GString mit einem Logger wie log4j unterhalb des Protokollierungsschwellenwerts protokollieren, wird der GString überhaupt nicht konvertiert.
Ataylor
1
Was im Test fehlt, ist StringBuilder mit berechneter Kapazität. Der Grund ist, dass foo + bar + baz eine oder zwei Puffererweiterungen verursacht, die die Zeit verlängern.
Ruralcoder
18
def my_string = "some string"
println "here: " + my_string 

Ich bin mir nicht ganz sicher, warum die obige Antwort in Benchmarks, String-Puffer, Tests usw. gehen muss.

Snowcrash
quelle
1
Der Einfachheit halber positiv bewerten. Ich muss nur zwei Zeichenfolgen verketten. lol
harperville
1

Reproduzieren der Antwort tim_yates auf der aktuellen Hardware und Hinzufügen der Methoden leftShift () und concat (), um den Befund zu überprüfen:

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

Das Ergebnis zeigt, dass concat () die schnellere Lösung für einen reinen String ist. Wenn Sie jedoch GString an einem anderen Ort verarbeiten können, liegt die GString-Vorlage noch vor Ihnen, während die Erwähnung mit leftShift () (bitweiser Operator) und StringBuffer () mit initial erfolgen sollte Zuweisung:

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
quelle