Конкатенация строк с помощью Groovy

91

Каков наилучший (идиоматический) способ объединения строк в Groovy?

Опция 1:

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

Вариант 2:

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

Я обнаружил интересный момент по этой теме на старом веб-сайте Groovy: то, что вы можете сделать, но лучше не делать.

Как и в Java, вы можете объединять строки с помощью символа «+». Но Java требуется, чтобы один из двух элементов выражения «+» был строкой, независимо от того, находится ли он на первом или последнем месте. Java будет использовать метод toString () в объекте, отличном от String, в вашем выражении "+". Но в Groovy вы просто должны быть в безопасности: первый элемент вашего выражения «+» правильно реализует метод plus (), потому что Groovy будет искать и использовать его. В Groovy GDK только классы Number и String / StringBuffer / Character имеют метод plus (), реализованный для объединения строк. Чтобы избежать сюрпризов, всегда используйте GStrings.

Артуро Эрреро
источник

Ответы:

122

Я всегда выбираю второй метод (с использованием шаблона GString), хотя, когда параметров больше, чем у вас, я стараюсь обернуть их, ${X}поскольку считаю, что это делает его более читаемым.

Выполнение некоторых тестов (с использованием превосходного модуля GBench Nagai Masato ) по этим методам также показывает, что создание шаблонов работает быстрее, чем другие методы:

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

Это дает мне следующий результат на моей машине:

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

Так что, учитывая удобочитаемость и скорость, я бы рекомендовал использовать шаблоны ;-)

NB: если вы добавите toString()в конец методов GString, чтобы тип вывода был таким же, как и другие метрики, и сделал его более справедливым тестом, StringBuilderиStringBuffer превзойти методы GString по скорости. Однако, поскольку GString может использоваться вместо String для большинства вещей (вам просто нужно проявлять осторожность с ключами карты и операторами SQL), его в большинстве случаев можно оставить без этого окончательного преобразования.

Добавление этих тестов (как было предложено в комментариях)

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

Теперь получаем результат:

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

Итак, как вы можете видеть (как я уже сказал), он медленнее, чем StringBuilder или StringBuffer, но все же немного быстрее, чем добавление строк ...

Но все же намного читабельнее.

Редактировать после комментария сельского кодера ниже

Обновлено до последней версии gbench, большие строки для конкатенации и тест с StringBuilder, инициализированным до подходящего размера:

@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
tim_yates
источник
3
Я не возражаю против использования шаблонов GString для удобочитаемости, но вам следует повторно запустить тесты, .toString()добавив к ним два теста GString. Мой пробег показывает, что тогда они работают почти так же, как String adder. Я предполагаю, что запущенный вами тест на самом деле не обрабатывает конкатенацию, поэтому он просто создает объект GString и сохраняет ссылки. StringBuilderпо-прежнему самый быстрый, если вам понадобится Stringв какой-то момент.
OverZealous 06
1
Я как-то пропустил вторую половину! Конечно, даже если вы оставите GString«как есть», в какой-то момент его нужно преобразовать в истинное String(даже просто для того, чтобы распечатать), поэтому истинное время будет последним установленным. В конце концов, когда время так близко, удобочитаемость GStringшаблонов бьет StringBuilderключом, так что это спорный вопрос. :-)
OverZealous 06
2
@OverZealous Аааа, да, как всегда, есть ложь, проклятая ложь и тесты ;-) Я считаю, что удобочитаемость является ключевым моментом, и, поскольку мы уже используем Groovy, мы заявили, что производительность на «голом железе» не является нашей главной задачей; -)
tim_yates 06
1
Да, одно из больших преимуществ GStrings в том, что они не конвертируются в строки до последнего момента. Это означает, например, что если вы регистрируете GString с помощью регистратора, такого как log4j, ниже порога регистрации, GString вообще никогда не конвертируется.
ataylor 06
1
Чего не хватает в тесте, так это StringBuilder с расчетной емкостью. Причина в том, что foo + bar + baz вызовет одно или два расширения буфера, что увеличивает время.
сельский кодер,
19
def my_string = "some string"
println "here: " + my_string 

Не совсем уверен, почему приведенный выше ответ должен входить в тесты, строковые буферы, тесты и т. Д.

Snowcrash
источник
2
Проголосуйте за простоту. Мне просто нужно объединить две строки. lol
harperville
2

Воспроизведение ответа tim_yates на текущем оборудовании и добавление методов leftShift () и concat () для проверки результатов:

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

Результат показывает, что concat () является более быстрым решением для чистой String, но если вы можете обрабатывать GString где-то еще, шаблон GString все еще впереди, в то время как почетное упоминание должно быть уделено leftShift () (побитовый оператор) и StringBuffer () с начальным распределение:

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
Торок
источник