Łączenie ciągów z Groovym

91

Jaki jest najlepszy (idiomatyczny) sposób łączenia ciągów znaków w Groovy?

Opcja 1:

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

Opcja 2:

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

Założyłem interesujący punkt na ten temat na starej stronie Groovy: Rzeczy, które możesz zrobić, ale lepiej zostaw to cofnięte.

Podobnie jak w Javie, możesz łączyć łańcuchy z symbolem „+”. Ale Java potrzebuje tylko jednego z dwóch elementów wyrażenia „+”, aby był ciągiem, bez względu na to, czy jest na pierwszym, czy na ostatnim miejscu. Java użyje metody toString () w obiekcie innym niż String wyrażenia „+”. Ale w Groovy powinieneś być bezpieczny, że pierwsza pozycja twojego wyrażenia "+" implementuje metodę plus () we właściwy sposób, ponieważ Groovy będzie go szukał i używał. W Groovy GDK tylko klasy Number i String / StringBuffer / Character mają zaimplementowaną metodę plus () do łączenia łańcuchów. Aby uniknąć niespodzianek, zawsze używaj GStrings.

Arturo Herrero
źródło

Odpowiedzi:

122

Zawsze wybieram drugą metodę (używając szablonu GString), chociaż gdy jest więcej niż kilka parametrów, takich jak ty, staram się je zawijać, ${X}ponieważ uważam, że jest to bardziej czytelne.

Uruchomienie niektórych testów porównawczych (przy użyciu doskonałego modułu GBench Nagai Masato ) na tych metodach również pokazuje, że tworzenie szablonów jest szybsze niż w przypadku innych metod:

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

To daje mi następujący wynik na moim komputerze:

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

Więc mając na względzie czytelność i szybkość, polecam tworzenie szablonów ;-)

Uwaga: jeśli dodasz toString()na końcu metody GString, aby typ wyniku był taki sam jak inne metryki i uczynił go bardziej sprawiedliwym testem, StringBuilderi StringBufferpokonał metody GString pod względem szybkości. Jednakże, ponieważ GString może być używany zamiast String dla większości rzeczy (wystarczy zachować ostrożność przy użyciu kluczy Map i instrukcji SQL), można go przeważnie pozostawić bez tej ostatecznej konwersji

Dodanie tych testów (o co pytano w komentarzach)

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

Teraz otrzymujemy wyniki:

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

Jak widać (jak powiedziałem), jest wolniejszy niż StringBuilder lub StringBuffer, ale wciąż nieco szybszy niż dodawanie ciągów znaków ...

Ale wciąż dużo bardziej czytelny.

Edytuj po komentarzu wiejscodera poniżej

Zaktualizowano do najnowszego gbencha, większych ciągów do konkatenacji i testu z inicjalizacją StringBuilder do dobrego rozmiaru:

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

daje

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
źródło
3
Nie zgadzam się z używaniem szablonów GString w celu zapewnienia czytelności, ale należy ponownie uruchomić testy z .toString()dołączeniem do dwóch testów GString. Mój bieg pokazuje, że wtedy działają prawie tak samo jak String adder. Domyślam się, że test, który przeprowadziłeś, tak naprawdę nie obsługuje konkatenacji, więc po prostu tworzy obiekt GString i przechowuje referencje. StringBuilderjest nadal najszybszy, bez dwóch zdań, jeśli będziesz potrzebować Stringw którymś momencie.
OverZealous
1
Jakoś tęskniłem za drugą połową! Oczywiście, nawet jeśli zostawisz GString"takie, jakie jest", w pewnym momencie musi zostać przekonwertowane na prawdziwe String(nawet po to, aby je wydrukować), więc prawdziwy czas jest ostatnim ustawieniem. W końcu czytelność GStringszablonów bije, StringBuildergdy czas jest tak blisko, więc to dyskusja. :-)
OverZealous
2
@OverZealous Ahhh tak, jak zawsze, są kłamstwa, przeklęte kłamstwa i testy wzorcowe ;-) Czytelność jest tutaj kluczowa, a ponieważ używamy już Groovy, stwierdziliśmy, że wydajność gołego metalu nie jest naszym głównym celem; -)
tim_yates,
1
Tak, jedną z największych zalet GStrings jest to, że nie są one konwertowane na łańcuchy aż do ostatniej chwili. Co oznacza, na przykład, jeśli zapiszesz GString za pomocą loggera takiego jak log4j poniżej progu rejestrowania, GString nigdy nie zostanie w ogóle przekonwertowany.
ataylor
1
To, czego brakuje w teście, to StringBuilder z obliczoną wydajnością. Powodem jest to, że foo + bar + baz spowoduje jedno lub dwa rozszerzenia bufora, które dodają się do czasu.
ruralcoder
19
def my_string = "some string"
println "here: " + my_string 

Nie do końca jestem pewien, dlaczego powyższa odpowiedź musi przejść do testów porównawczych, buforów ciągów, testów itp.

Snowcrash
źródło
2
Głosuj za prostotą. Muszę tylko połączyć dwa ciągi. lol
harperville
2

Odtworzenie odpowiedzi tim_yates na aktualnym sprzęcie i dodanie metody leftShift () i concat () w celu sprawdzenia wyniku:

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

Wynik pokazuje, że concat () jest szybszym rozwiązaniem dla czystego ciągu znaków, ale jeśli możesz obsłużyć GString gdzie indziej, szablon GString jest nadal przed nami, podczas gdy honorowa wzmianka powinna przejść do leftShift () (operator bitowy) i StringBuffer () z początkiem przydział:

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
źródło