【问题标题】:String concatenation in RubyRuby 中的字符串连接
【发布时间】:2010-09-27 12:37:05
【问题描述】:

我正在寻找一种在 Ruby 中连接字符串的更优雅的方式。

我有以下行:

source = "#{ROOT_DIR}/" << project << "/App.config"

有更好的方法吗?

那么&lt;&lt;+ 之间有什么区别?

【问题讨论】:

标签: ruby string-concatenation


【解决方案1】:

您可以通过多种方式做到这一点:

  1. 正如您在&lt;&lt; 中展示的那样,但这不是通常的方式
  2. 带字符串插值

    source = "#{ROOT_DIR}/#{project}/App.config"
    
  3. +

    source = "#{ROOT_DIR}/" + project + "/App.config"
    

从我所见(虽然没有测量),第二种方法在内存/速度方面似乎更有效。当 ROOT_DIR 为 nil 时,这三个方法都会抛出未初始化的常量错误。

在处理路径名时,您可能希望使用File.join 以避免混淆路径名分隔符。

最终,这是一个品味问题。

【讨论】:

  • 我对 ruby​​ 不是很有经验。但通常在您连接大量字符串的情况下,您通常可以通过将字符串附加到数组然后最后以原子方式将字符串组合在一起来获得性能。那么
  • 无论如何,您都必须添加内存并将较长的字符串复制到其中。
  • 不是在数组元素上使用
【解决方案2】:

由于这是一条路径,我可能会使用数组并加入:

source = [ROOT_DIR, project, 'App.config'] * '/'

【讨论】:

    【解决方案3】:

    如果你只是连接路径,你可以使用 Ruby 自己的 File.join 方法。

    source = File.join(ROOT_DIR, project, 'App.config')
    

    【讨论】:

    • 这似乎是要走的路,因为那时 ruby​​ 将负责在具有不同路径分隔符的系统上创建正确的字符串。
    【解决方案4】:

    + 运算符是正常的连接选择,可能是连接字符串的最快方法。

    +&lt;&lt; 之间的区别在于&lt;&lt; 更改其左侧的对象,而+ 不会。

    irb(main):001:0> s = 'a'
    => "a"
    irb(main):002:0> s + 'b'
    => "ab"
    irb(main):003:0> s
    => "a"
    irb(main):004:0> s << 'b'
    => "ab"
    irb(main):005:0> s
    => "ab"
    

    【讨论】:

    • + 运算符绝对不是连接字符串的最快方法。每次使用它时,它都会生成一个副本,而
    • 对于大多数用途,插值,+&lt;&lt; 将大致相同。如果您正在处理很多字符串,或者非常大的字符串,那么您可能会注意到不同之处。我对他们的表现如此相似感到惊讶。 gist.github.com/2895311
    • 由于早期运行的 JVM 过载,您的 jruby 结果与插值不一致。如果您为每个解释器多次运行测试套件(在同一个过程中 - 所以将所有内容包装在 5.times do ... end 块中),您最终会得到更准确的结果。我的测试表明插值是所有 Ruby 解释器中最快的方法。我原以为 &lt;&lt; 是最快的,但这就是我们进行基准测试的原因。
    • 对Ruby不太熟悉,我很好奇突变是在栈上还是堆上执行的?如果在堆上,即使是看起来应该更快的变异操作,也可能涉及某种形式的 malloc。没有它,我希望缓冲区溢出。使用堆栈可能非常快,但结果值可能无论如何都放在堆上,需要 malloc 操作。最后,我希望内存指针是一个新地址,即使变量引用使它看起来像一个就地突变。那么,真的有区别吗?
    【解决方案5】:

    我更喜欢使用路径名:

    require 'pathname' # pathname is in stdlib
    Pathname(ROOT_DIR) + project + 'App.config'
    

    关于 &lt;&lt;+ 来自 ruby​​ 文档:

    +:返回一个 new 字符串,其中包含连接到 str 的 other_str

    &lt;&lt;:将给定对象连接到 str。如果对象是一个介于 0 和 255 之间的 Fixnum,则在连接之前将其转换为字符。

    所以区别在于第一个操作数(&lt;&lt; 进行更改,+ 返回新字符串,因此内存更重)以及如果第一个操作数是 Fixnum(&lt;&lt; 将添加好像它是代码等于该数字的字符,+ 将引发错误)

    【讨论】:

    • 我刚刚发现在路径名上调用“+”可能很危险,因为如果 arg 是绝对路径,接收器路径将被忽略:Pathname('/home/foo') + '/etc/passwd' # =&gt; #&lt;Pathname:/etc/passwd&gt;。这是设计使然,基于 ruby​​doc 示例。似乎 File.join 更安全。
    • 如果要返回字符串对象,还需要调用(Pathname(ROOT_DIR) + project + 'App.config').to_s
    【解决方案6】:

    让我向你展示我在这方面的所有经验。

    我有一个返回 32k 记录的查询,对于每条记录,我调用一个方法将该数据库记录格式化为格式化字符串,然后将其连接成一个字符串,在所有这些过程结束时将变成一个文件磁盘。

    我的问题是,据记录,大约 24k,连接字符串的过程很痛苦。

    我是用常规的“+”运算符来做的。

    当我更改为“

    所以,我想起了我的旧时光——大概是 1998 年——当时我使用 Java 并使用“+”连接字符串并从 String 更改为 StringBuffer(现在我们,Java 开发人员拥有 StringBuilder)。

    我相信Ruby世界中+/

    第一个在内存中重新分配整个对象,另一个只是指向一个新地址。

    【讨论】:

      【解决方案7】:

      这里有更多方法可以做到这一点:

      "String1" + "String2"
      
      "#{String1} #{String2}"
      
      String1<<String2
      

      等等……

      【讨论】:

        【解决方案8】:

        你说的串联?那么#concat方法呢?

        a = 'foo'
        a.object_id #=> some number
        a.concat 'bar' #=> foobar
        a.object_id #=> same as before -- string a remains the same object
        

        平心而论,concat 别名为&lt;&lt;

        【讨论】:

        • 还有一种其他人没有提到的将字符串粘合在一起的方法,那就是并列:"foo" "bar" 'baz" #=&gt; "foobarabaz"
        • 请注意:这不应该是单引号,而是像其他引号一样的双引号。简洁的方法!
        【解决方案9】:

        来自http://greyblake.com/blog/2012/09/02/ruby-perfomance-tricks/

        使用&lt;&lt; 又名concat+= 高效得多,因为后者会创建一个临时对象并用新对象覆盖第一个对象。

        require 'benchmark'
        
        N = 1000
        BASIC_LENGTH = 10
        
        5.times do |factor|
          length = BASIC_LENGTH * (10 ** factor)
          puts "_" * 60 + "\nLENGTH: #{length}"
        
          Benchmark.bm(10, '+= VS <<') do |x|
            concat_report = x.report("+=")  do
              str1 = ""
              str2 = "s" * length
              N.times { str1 += str2 }
            end
        
            modify_report = x.report("<<")  do
              str1 = "s"
              str2 = "s" * length
              N.times { str1 << str2 }
            end
        
            [concat_report / modify_report]
          end
        end
        

        输出:

        ____________________________________________________________
        LENGTH: 10
                         user     system      total        real
        +=           0.000000   0.000000   0.000000 (  0.004671)
        <<           0.000000   0.000000   0.000000 (  0.000176)
        += VS <<          NaN        NaN        NaN ( 26.508796)
        ____________________________________________________________
        LENGTH: 100
                         user     system      total        real
        +=           0.020000   0.000000   0.020000 (  0.022995)
        <<           0.000000   0.000000   0.000000 (  0.000226)
        += VS <<          Inf        NaN        NaN (101.845829)
        ____________________________________________________________
        LENGTH: 1000
                         user     system      total        real
        +=           0.270000   0.120000   0.390000 (  0.390888)
        <<           0.000000   0.000000   0.000000 (  0.001730)
        += VS <<          Inf        Inf        NaN (225.920077)
        ____________________________________________________________
        LENGTH: 10000
                         user     system      total        real
        +=           3.660000   1.570000   5.230000 (  5.233861)
        <<           0.000000   0.010000   0.010000 (  0.015099)
        += VS <<          Inf 157.000000        NaN (346.629692)
        ____________________________________________________________
        LENGTH: 100000
                         user     system      total        real
        +=          31.270000  16.990000  48.260000 ( 48.328511)
        <<           0.050000   0.050000   0.100000 (  0.105993)
        += VS <<   625.400000 339.800000        NaN (455.961373)
        

        【讨论】:

          【解决方案10】:

          这是另一个受this gist 启发的基准测试。它比较动态和预定义字符串的串联 (+)、附加 (&lt;&lt;) 和插值 (#{})。

          require 'benchmark'
          
          # we will need the CAPTION and FORMAT constants:
          include Benchmark
          
          count = 100_000
          
          
          puts "Dynamic strings"
          
          Benchmark.benchmark(CAPTION, 7, FORMAT) do |bm|
            bm.report("concat") { count.times { 11.to_s +  '/' +  12.to_s } }
            bm.report("append") { count.times { 11.to_s << '/' << 12.to_s } }
            bm.report("interp") { count.times { "#{11}/#{12}" } }
          end
          
          
          puts "\nPredefined strings"
          
          s11 = "11"
          s12 = "12"
          Benchmark.benchmark(CAPTION, 7, FORMAT) do |bm|
            bm.report("concat") { count.times { s11 +  '/' +  s12 } }
            bm.report("append") { count.times { s11 << '/' << s12 } }
            bm.report("interp") { count.times { "#{s11}/#{s12}"   } }
          end
          

          输出:

          Dynamic strings
                        user     system      total        real
          concat    0.050000   0.000000   0.050000 (  0.047770)
          append    0.040000   0.000000   0.040000 (  0.042724)
          interp    0.050000   0.000000   0.050000 (  0.051736)
          
          Predefined strings
                        user     system      total        real
          concat    0.030000   0.000000   0.030000 (  0.024888)
          append    0.020000   0.000000   0.020000 (  0.023373)
          interp    3.160000   0.160000   3.320000 (  3.311253)
          

          结论:MRI 中的插值很繁重。

          【讨论】:

          • 由于字符串现在开始是不可变的,我很想看到一个新的基准。
          【解决方案11】:

          您可以使用+&lt;&lt; 运算符,但在ruby 中.concat 函数是最可取的,因为它比其他运算符快得多。你可以像这样使用它。

          source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))
          

          【讨论】:

          • 我认为你在最后一个concat 之后还有一个额外的. 没有?
          【解决方案12】:

          情况很重要,例如:

          # this will not work
          output = ''
          
          Users.all.each do |user|
            output + "#{user.email}\n"
          end
          # the output will be ''
          puts output
          
          # this will do the job
          output = ''
          
          Users.all.each do |user|
            output << "#{user.email}\n"
          end
          # will get the desired output
          puts output
          

          在第一个示例中,与+ 运算符连接不会更新output 对象,但是,在第二个示例中,&lt;&lt; 运算符将在每次迭代时更新output 对象。所以,对于上述类型的情况,&lt;&lt; 更好。

          【讨论】:

            【解决方案13】:

            您也可以使用%,如下:

            source = "#{ROOT_DIR}/%s/App.config" % project
            

            这种方法也适用于'(单引号)。

            【讨论】:

              【解决方案14】:

              您可以直接在字符串定义中连接:

              nombre_apellido = "#{customer['first_name']} #{customer['last_name']} #{order_id}"
              

              【讨论】:

                【解决方案15】:

                对于您的特定情况,您还可以在构造字符串的文件路径类型时使用Array#join

                string = [ROOT_DIR, project, 'App.config'].join('/')]
                

                这有一个令人愉快的副作用,即自动将不同类型转换为字符串:

                ['foo', :bar, 1].join('/')
                =>"foo/bar/1"
                

                【讨论】:

                  【解决方案16】:

                  对于木偶:

                  $username = 'lala'
                  notify { "Hello ${username.capitalize}":
                      withpath => false,
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2011-07-05
                    • 2014-11-14
                    • 2011-03-11
                    • 2011-05-23
                    • 1970-01-01
                    • 2013-01-03
                    • 2015-07-11
                    • 2011-01-26
                    相关资源
                    最近更新 更多