390

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

我有以下行:

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

有没有更好的方法来做到这一点?

<<就此而言和有什么区别+

4

16 回答 16

606

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

  1. 正如你所展示的,<<但这不是通常的方式
  2. 使用字符串插值

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

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

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

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

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

于 2008-12-18T13:09:06.457 回答
104

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

+和之间的区别在于<<<<改变了左侧的对象,而+没有。

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"
于 2008-12-18T15:50:08.363 回答
80

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

source = File.join(ROOT_DIR, project, 'App.config')
于 2008-12-18T13:22:32.373 回答
29

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

使用<<akaconcat比使用 aka 更有效+=,因为后者会创建一个临时对象并用新对象覆盖第一个对象。

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)
于 2015-02-19T23:22:23.013 回答
11

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

source = [ROOT_DIR, project, 'App.config'] * '/'
于 2008-12-18T13:14:23.000 回答
9

这是受此要点启发的另一个基准。它比较动态和预定义字符串的串联 ( +)、附加 ( <<) 和插值 ( #{})。

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中的插值很重。

于 2016-09-15T09:13:35.390 回答
7

我更喜欢使用路径名:

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

关于<<+来自 ruby​​ 文档:

+:返回一个包含连接到 str 的 other_str的新字符串

<<: 将给定对象连接到 str。如果对象是 0 到 255 之间的 Fixnum,则在连接之前将其转换为字符。

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

于 2009-12-26T06:27:20.067 回答
6

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

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

我的问题是,根据记录,在 24k 左右,连接字符串的过程很痛苦。

我正在使用常规的“+”运算符来做到这一点。

当我改为'<<'时就像魔术一样。真的很快。

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

我相信Ruby世界中+/<<的过程和Java世界中的+/StringBuilder.append是一样的。

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

于 2012-08-03T20:11:33.363 回答
5

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

"String1" + "String2"

"#{String1} #{String2}"

String1<<String2

等等 ...

于 2013-03-06T20:11:41.827 回答
5

你说的串联?那方法呢#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别名为<<.

于 2013-06-12T03:48:00.470 回答
2

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

source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))
于 2018-10-09T05:26:41.017 回答
2

您也可以%按如下方式使用:

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

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

于 2019-02-19T13:56:51.270 回答
2

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

nombre_apellido = "#{customer['first_name']} #{customer['last_name']} #{order_id}"
于 2019-03-29T01:21:11.290 回答
1

情况很重要,例如:

# 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对象,但是在第二个示例中,运算符将在每次迭代<<时更新对象。output所以,对于上述类型的情况,<<是比较好的。

于 2019-02-06T07:34:56.177 回答
0

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

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

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

['foo', :bar, 1].join('/')
=>"foo/bar/1"
于 2019-11-27T23:28:37.050 回答
0

对于木偶:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
于 2020-03-05T16:13:14.800 回答