Ruby中的字符串串联


Answers:


574

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

  1. 如您所显示,<<但这不是通常的方式
  2. 带字符串插补

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

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

第二种方法在内存/速度方面似乎比我所见的更为有效(尽管未测量)。当ROOT_DIR为nil时,这三种方法都将引发未初始化的常量错误。

在处理路径名时,您可能需要File.join避免使用路径名分隔符。

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


7
我对红宝石不是很有经验。但是通常在连接许多字符串的情况下,通常可以通过将字符串附加到数组中,然后最后以原子方式将字符串组合在一起来获得性能。那么<<可能有用吗?
PEZ

1
无论如何,您都必须添加内存,将更长的字符串复制到其中。<<与+几乎相同,除了可以用单个字符<<。
凯尔蒂亚

9
不用在数组的元素上使用<<,而是使用Array#join,它要快得多。
格兰特·哈钦斯

94

+操作是正常级联选择,可能是连接字符串的最快方法。

+和之间的区别在于,<<<<更改了对象的左侧,而+不会更改。

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"

32
+运算符绝对不是连接字符串的最快方法。每次使用它时,它都会创建一个副本,而<<会串联在一起并且性能更高。
邪恶鳟鱼2012年

5
对于大多数用途,插值,+并且<<将是大致相同的。如果您要处理很多或非常大的字符串,那么您可能会发现有所不同。他们的表演如此相似,令我感到惊讶。gist.github.com/2895311
马特·伯克

8
早期的JVM重载会使您的jruby结果偏向于插值。如果您5.times do ... end为每个解释器多次运行测试套件(以相同的过程-将所有内容包装成一个块),最终将得到更准确的结果。我的测试表明,在所有Ruby解释器中,插值是最快的方法。我本来希望<<是最快的,但这就是我们进行基准测试的原因。
womble 2012年

不太熟悉Ruby,我很好奇这个突变是在堆栈还是在堆上执行?如果在堆上,甚至看起来应该更快的变异操作,也可能涉及某种形式的malloc。没有它,我期望缓冲区溢出。使用堆栈可能很快,但是无论如何结果值都可能放在堆上,需要进行malloc操作。最后,我希望内存指针是一个新地址,即使变量引用使它看起来像是就地突变。那么,真的有区别吗?
罗宾·科

79

如果只是串联路径,则可以使用Ruby自己的File.join方法。

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

5
这似乎是可行的方法,因为ruby将负责在具有不同路径分隔符的系统上创建正确的字符串。
PEZ

26

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

使用<<aka concat比使用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)


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内插较重。


由于字符串现在已经变得不可变了,我很乐意为此找到一个新的基准。
bibstha

7

我更喜欢使用路径名:

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

关于<<+来自ruby文档:

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

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

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


2
我刚刚发现在路径名上调用'+'可能很危险,因为如果arg是绝对路径,则接收器路径将被忽略:Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>。这是基于rubydoc示例设计的。似乎File.join更安全。
开尔文

(Pathname(ROOT_DIR) + project + 'App.config').to_s如果要返回字符串对象,也需要调用。
lacostenycoder,

6

让我向您展示我的所有经验。

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

我的问题是,根据记录,大约24k左右,连接String的过程变得很痛苦。

我正在使用常规的'+'运算符。

当我更改为“ <<”时,就像魔术一样。真的很快。

因此,我想起了我的旧时代-大约1998年-我当时使用Java并使用'+'连接String并将其从String更改为StringBuffer(现在,我们Java开发人员拥有StringBuilder)。

我相信Ruby世界中+ / <<的过程与Java世界中的+ / StringBuilder.append相同。

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


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别名为<<


7
还有一种将线粘在一起的方法,这是别人没有提到的,而仅仅是并列:"foo" "bar" 'baz" #=> "foobarabaz"
Boris Stitnicky

给其他人的提示:这不应该是单引号,而是像其他引号一样的双引号。整洁的方法!
Joshua Pinter


2

您还可以%如下使用:

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

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


2

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

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

我想.您上一次concat没有之后还有额外的钱吗?
lacostenycoder

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每次迭代时更新对象。因此,对于上述类型的情况,<<更好。


1

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

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

0

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

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

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

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

0

对于木偶:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.