【问题标题】:String concatenation with Groovy使用 Groovy 进行字符串连接
【发布时间】:2012-07-06 17:50:06
【问题描述】:

在 Groovy 中连接字符串的最佳(惯用)方法是什么?

选项 1:

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

选项 2:

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

我在旧的 Groovy 网站上就这个主题提出了一个有趣的观点:可以做的事情,但最好不要做。

在 Java 中,您可以使用“+”符号连接字符串。但是Java 只需要“+”表达式的两项之一是 字符串,不管它是在第一个还是在最后一个。爪哇 将在“+”的非字符串对象中使用 toString() 方法 表达。但是在 Groovy 中,你应该是安全的 您的“+”表达式以正确的方式实现了 plus() 方法, 因为 Groovy 会搜索并使用它。在 Groovy GDK 中,只有数字 和 String/StringBuffer/Character 类有 plus() 方法 实现连接字符串。为避免意外,请始终使用 GStrings。

【问题讨论】:

    标签: string groovy idioms string-concatenation gstring


    【解决方案1】:

    我总是选择第二种方法(使用 GString 模板),但是当像您这样有多个参数时,我倾向于将它们包装在 ${X} 中,因为我发现它更具可读性。

    在这些方法上运行一些基准测试(使用Nagai Masato 的优秀GBench module)也表明模板比其他方法更快:

    @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() 以使输出类型与其他指标相同,并使其成为更公平的测试,StringBuilderStringBuffer 会在速度方面击败 GString 方法.然而,由于 GString 可以在大多数情况下代替 String (您只需要谨慎使用 Map 键和 SQL 语句),因此大多数情况下都可以不进行最终转换

    添加这些测试(如 cmets 中所要求的那样)

      '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
    

    【讨论】:

    • 我不反对使用 GString 模板来提高可读性,但您应该在两个 GString 测试后附加 .toString() 重新运行测试。我的运行表明它们的性能几乎与String adder 相同。我的猜测是您运行的测试实际上并没有处理连接,所以它只是创建一个 GString 对象并存储引用。 StringBuilder 仍然是最快的,如果您在某个时候需要 String,请放心。
    • 不知何故我错过了下半场!当然,即使您将 GString 保留为“原样”,在 some 点它也必须转换为真正的 String,(即使只是打印出来),所以真正的时机是最后一组。最后,当时间如此接近时,GString 模板的易读性胜过StringBuilder,所以它没有实际意义。 :-)
    • @OverZealous Ahhh 是的,一如既往,有lies, damned lies and benchmarks ;-) 我觉得可读性是关键,因为我们已经在使用 Groovy,我们已经声明裸机性能是不是我们的主要考虑;-)
    • 是的,GStrings 的一大优点是它们直到最后一刻才转换为字符串。这意味着,例如,如果您使用低于日志记录阈值的 log4j 记录器记录 GString,则 GString 根本不会被转换。
    • 测试中缺少的是具有计算容量的 StringBuilder。原因是 foo+bar+baz 会导致一两次缓冲区扩展,这会增加时间。
    【解决方案2】:
    def my_string = "some string"
    println "here: " + my_string 
    

    不太清楚为什么上面的答案需要进入基准测试、字符串缓冲区、测试等。

    【讨论】:

    • 为简单起见点赞。我只需要连接两个字符串。大声笑
    【解决方案3】:

    在当前硬件上重现 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-02
      • 2011-12-23
      • 2020-03-29
      • 2018-08-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多