我正在寻找一种在Ruby中连接字符串的更优雅的方法。
我有以下几行:
source = "#{ROOT_DIR}/" << project << "/App.config"
有更好的方法吗?
那么,<<
和之间有什么区别+
?
我正在寻找一种在Ruby中连接字符串的更优雅的方法。
我有以下几行:
source = "#{ROOT_DIR}/" << project << "/App.config"
有更好的方法吗?
那么,<<
和之间有什么区别+
?
Answers:
您可以通过几种方式来做到这一点:
<<
但这不是通常的方式带字符串插补
source = "#{ROOT_DIR}/#{project}/App.config"
与 +
source = "#{ROOT_DIR}/" + project + "/App.config"
第二种方法在内存/速度方面似乎比我所见的更为有效(尽管未测量)。当ROOT_DIR为nil时,这三种方法都将引发未初始化的常量错误。
在处理路径名时,您可能需要File.join
避免使用路径名分隔符。
最后,这是一个品味问题。
该+
操作是正常级联选择,可能是连接字符串的最快方法。
+
和之间的区别在于,<<
它<<
更改了对象的左侧,而+
不会更改。
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"
5.times do ... end
为每个解释器多次运行测试套件(以相同的过程-将所有内容包装成一个块),最终将得到更准确的结果。我的测试表明,在所有Ruby解释器中,插值是最快的方法。我本来希望<<
是最快的,但这就是我们进行基准测试的原因。
从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)
由于这是一条路径,因此我可能会使用数组和联接:
source = [ROOT_DIR, project, 'App.config'] * '/'
这是受此要旨启发的另一个基准。它比较动态字符串和预定义字符串的串联(+
),附加(<<
)和插值(#{}
)。
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内插较重。
我更喜欢使用路径名:
require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'
关于<<
和+
来自ruby文档:
+
:返回一个新字符串,该字符串包含连接到str的other_str
<<
:将给定的对象连接到str。如果对象是介于0到255之间的Fixnum,则在连接之前将其转换为字符。
所以区别在于第一个操作数变成什么(<<
在原位置进行更改,+
返回新的字符串,因此它会增加内存),如果第一个操作数是Fixnum(<<
将会添加,就好像它是具有等于该数字的代码的字符,+
则将增加)错误)
Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>
。这是基于rubydoc示例设计的。似乎File.join更安全。
(Pathname(ROOT_DIR) + project + 'App.config').to_s
如果要返回字符串对象,也需要调用。
让我向您展示我的所有经验。
我有一个查询,该查询返回了32k条记录,对于每条记录,我都调用了一种方法,该方法将数据库记录格式化为格式化的字符串,然后将其连接为字符串,在所有此过程结束时,该字符串将转换为磁盘中的文件。
我的问题是,根据记录,大约24k左右,连接String的过程变得很痛苦。
我正在使用常规的'+'运算符。
当我更改为“ <<”时,就像魔术一样。真的很快。
因此,我想起了我的旧时代-大约1998年-我当时使用Java并使用'+'连接String并将其从String更改为StringBuffer(现在,我们Java开发人员拥有StringBuilder)。
我相信Ruby世界中+ / <<的过程与Java世界中的+ / StringBuilder.append相同。
第一个在内存中重新分配整个对象,另一个仅指向新地址。
你说级联?那#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
别名为<<
。
"foo" "bar" 'baz" #=> "foobarabaz"
以下是执行此操作的更多方法:
"String1" + "String2"
"#{String1} #{String2}"
String1<<String2
等等 ...
您可以使用+
或<<
运算符,但在ruby .concat
函数中是最可取的,因为它比其他运算符快得多。您可以像使用它。
source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))
.
您上一次concat
没有之后还有额外的钱吗?
情况很重要,例如:
# 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
每次迭代时更新对象。因此,对于上述类型的情况,<<
更好。
对于您的特殊情况,您还可以Array#join
在构造字符串的文件路径类型时使用:
string = [ROOT_DIR, project, 'App.config'].join('/')]
自动将不同类型转换为字符串具有令人愉快的副作用:
['foo', :bar, 1].join('/')
=>"foo/bar/1"