【发布时间】:2010-09-27 12:37:05
【问题描述】:
我正在寻找一种在 Ruby 中连接字符串的更优雅的方式。
我有以下行:
source = "#{ROOT_DIR}/" << project << "/App.config"
有更好的方法吗?
那么<< 和+ 之间有什么区别?
【问题讨论】:
-
这个问题stackoverflow.com/questions/4684446/… 高度相关。
-
我正在寻找一种在 Ruby 中连接字符串的更优雅的方式。
我有以下行:
source = "#{ROOT_DIR}/" << project << "/App.config"
有更好的方法吗?
那么<< 和+ 之间有什么区别?
【问题讨论】:
您可以通过多种方式做到这一点:
<< 中展示的那样,但这不是通常的方式带字符串插值
source = "#{ROOT_DIR}/#{project}/App.config"
+
source = "#{ROOT_DIR}/" + project + "/App.config"
从我所见(虽然没有测量),第二种方法在内存/速度方面似乎更有效。当 ROOT_DIR 为 nil 时,这三个方法都会抛出未初始化的常量错误。
在处理路径名时,您可能希望使用File.join 以避免混淆路径名分隔符。
最终,这是一个品味问题。
【讨论】:
由于这是一条路径,我可能会使用数组并加入:
source = [ROOT_DIR, project, 'App.config'] * '/'
【讨论】:
如果你只是连接路径,你可以使用 Ruby 自己的 File.join 方法。
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"
【讨论】:
+ 和 << 将大致相同。如果您正在处理很多字符串,或者非常大的字符串,那么您可能会注意到不同之处。我对他们的表现如此相似感到惊讶。 gist.github.com/2895311
5.times do ... end 块中),您最终会得到更准确的结果。我的测试表明插值是所有 Ruby 解释器中最快的方法。我原以为 << 是最快的,但这就是我们进行基准测试的原因。
我更喜欢使用路径名:
require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'
关于 << 和 + 来自 ruby 文档:
+:返回一个 new 字符串,其中包含连接到 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,连接字符串的过程很痛苦。
我是用常规的“+”运算符来做的。
当我更改为“
所以,我想起了我的旧时光——大概是 1998 年——当时我使用 Java 并使用“+”连接字符串并从 String 更改为 StringBuffer(现在我们,Java 开发人员拥有 StringBuilder)。
我相信Ruby世界中+/
第一个在内存中重新分配整个对象,另一个只是指向一个新地址。
【讨论】:
这里有更多方法可以做到这一点:
"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 别名为<<。
【讨论】:
"foo" "bar" 'baz" #=> "foobarabaz"
来自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)
【讨论】:
这是另一个受this gist 启发的基准测试。它比较动态和预定义字符串的串联 (+)、附加 (<<) 和插值 (#{})。
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"))
【讨论】:
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 对象。所以,对于上述类型的情况,<< 更好。
【讨论】:
您也可以使用%,如下:
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,
}
【讨论】: