使用Groovy进行字符串拼接

107
什么是在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() 方法。但在 Groovy 中,您应该确保您“+”表达式的第一个项目正确实现了 plus() 方法,因为 Groovy 将搜索并使用它。在 Groovy GDK 中,只有 Number 和 String/StringBuffer/Character 类实现了 plus() 方法以连接字符串。为避免出现意外情况,请始终使用 GStrings。

3个回答

129

我通常采用第二种方法(使用GString模板),但当像您这样有多个参数时,我倾向于将它们用${X}包装起来,因为我发现这样更易读。

在这些方法上运行一些基准测试(使用Nagai Masato的出色GBench模块)还显示出模板比其他方法更快:

@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
所以从易读性和速度方面考虑,我建议使用模板;-)
请注意:如果您在GString方法的末尾添加toString(),使输出类型与其他指标相同,并进行公平测试,则StringBuilder和StringBuffer在速度上胜过GString方法。但是,由于GString可以替代大多数String(只需小心处理Map键和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要慢,但仍然比添加字符串要快一些...

但是可读性更高。

在下面ruralcoder的评论后进行编辑

更新为最新的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

3
我不反对使用GString模板来提高可读性,但是你应该在两个GString测试后加上.toString()并重新运行测试。我的运行结果显示它们的性能几乎与String adder相同。我猜想你运行的测试实际上没有处理连接操作,只是创建了一个GString对象并存储了引用。如果你需要在某个时刻得到一个String,那么StringBuilder仍然是最快的选择。 - OverZealous
2
@OverZealous 啊,是的,一如既往,有“谎言、诅咒的谎言和基准测试”;-) 可读性在这里是关键,而且我们已经使用了Groovy,我们已经声明裸机性能不是我们主要考虑的因素;-) - tim_yates
1
测试中缺少计算容量的StringBuilder。原因是foo+bar+baz会导致一到两次缓冲区扩展,从而增加时间。 - ruralcoder
1
我使用一个真实的问题来运行自己的基准测试,最终字符串大约有256个字符长,但长度当然是可变的。使用容量计算的StringBuilder比GString快4倍。使用了100万次迭代。使用Groovy进行基准测试,使用currentTimeMillis的delta值。我还将尝试gbench,以前从未使用过。 - ruralcoder
1
@ruralcoder 是的,组件字符串大小有误导性...已更新答案。 - tim_yates
显示剩余6条评论

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

我不太确定为什么上面的回答需要涉及基准测试、字符串缓冲区、测试等内容。


14
点赞以获得简洁版翻译。我只需要将两个字符串连接起来。哈哈 - harperville

2

以下是在当前硬件上复制 tim_yates 的答案,并添加 leftShift() 和 concat() 方法以检查查找结果:

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

结果显示,对于纯字符串,concat()是更快的解决方案,但如果您可以在其他地方处理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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接