Ruby中的字符串连接

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

我有如下一行:

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

有更好的方法吗?

就此而言,<<+之间的区别是什么?

599870 次浏览

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

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

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

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

The second method seems to be more efficient in term of memory/speed from what I've seen (not measured though). All three methods will throw an uninitialized constant error when ROOT_DIR is nil.

When dealing with pathnames, you may want to use File.join to avoid messing up with pathname separator.

In the end, it is a matter of taste.

因为这是一个路径,我可能会使用数组和join:

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

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

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

+操作符是正常的连接选择,并且可能是连接字符串的最快方式。

+<<的区别在于<<改变了对象的左侧,而+则不会。

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"

我更喜欢使用Pathname:

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

关于ruby文档中的<<+:

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

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

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

让我给你们看看我的经验。

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

我的问题是,在24k左右,连接字符串的过程开始变得痛苦。

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

当我换成“<<”就像魔法一样。真的很快。

所以,我记得我的旧时光-大约1998年-当我使用Java和连接字符串使用'+',从字符串改为StringBuffer(现在我们,Java开发人员有StringBuilder)。

我相信+ / <<和+ / StringBuilder是一样的。在Java世界中追加。

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

这里有更多的方法:

"String1" + "String2"


"#{String1} #{String2}"


String1<<String2

等等……

你说的是串联?那么#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别名为<<

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

使用<<也就是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)

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

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

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

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

情况很重要,例如:

# 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对象。因此,对于上述类型的情况,<<更好。

你也可以像下面这样使用%:

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

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

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

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

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

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

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

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

木偶:

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