为啥在 Ruby 中构建字符串时,铲子运算符 (<<) 优于加号 (+=)?
Posted
技术标签:
【中文标题】为啥在 Ruby 中构建字符串时,铲子运算符 (<<) 优于加号 (+=)?【英文标题】:Why is the shovel operator (<<) preferred over plus-equals (+=) when building a string in Ruby?为什么在 Ruby 中构建字符串时,铲子运算符 (<<) 优于加号 (+=)? 【发布时间】:2011-06-08 17:44:42 【问题描述】:我正在研究 Ruby Koans。
about_strings.rb 中的test_the_shovel_operator_modifies_the_original_string
Koan 包含以下评论:
Ruby 程序员倾向于使用铲子运算符 (
我的猜测是它涉及速度,但我不明白引擎盖下的动作会导致铲子操作员更快。
有人能解释一下这个偏好背后的细节吗?
【问题讨论】:
shovel 操作符修改 String 对象,而不是创建一个新的 String 对象(消耗内存)。语法是不是很漂亮?参看。 Java 和 .NET 有 StringBuilder 类 【参考方案1】:证明:
a = 'foo'
a.object_id #=> 2154889340
a << 'bar'
a.object_id #=> 2154889340
a += 'quux'
a.object_id #=> 2154742560
所以<<
更改了原始字符串,而不是创建一个新字符串。这样做的原因是,在 ruby 中,a += b
是 a = a + b
的语法简写(其他 <op>=
运算符也是如此),这是一个赋值。另一方面,<<
是 concat()
的别名,它会就地改变接收器。
【讨论】:
谢谢,面条!那么,从本质上讲, This benchmark 表示Array#join
比使用<<
慢。
EdgeCase 的一个家伙发布了一个带有性能数字的解释:A Little More About Strings
以上@CincinnatiJoe 链接似乎已损坏,这是一个新链接:A Little More About Strings
对于java人员:Ruby中的'+'运算符对应于通过StringBuilder对象追加,'
【参考方案2】:
性能证明:
#!/usr/bin/env ruby
require 'benchmark'
Benchmark.bmbm do |x|
x.report('+= :') do
s = ""
10000.times s += "something "
end
x.report('<< :') do
s = ""
10000.times s << "something "
end
end
# Rehearsal ----------------------------------------
# += : 0.450000 0.010000 0.460000 ( 0.465936)
# << : 0.010000 0.000000 0.010000 ( 0.009451)
# ------------------------------- total: 0.470000sec
#
# user system total real
# += : 0.270000 0.010000 0.280000 ( 0.277945)
# << : 0.000000 0.000000 0.000000 ( 0.003043)
【讨论】:
【参考方案3】:一位正在学习 Ruby 作为他的第一门编程语言的朋友在浏览 Ruby Koans 系列的 Ruby 中的字符串时问了我同样的问题。我用下面的比喻向他解释过;
您的一杯水只喝了一半,需要重新装满。
第一种方法是取一个新玻璃杯,将水龙头中的水倒入一半,然后用第二个半满的玻璃杯重新装满您的饮水杯。每次您需要重新装满杯子时,您都会这样做。
第二种方法是你拿起半满的杯子,然后直接用水龙头里的水重新装满。
在一天结束时,如果您选择每次需要重新装满玻璃杯时都选择一个新玻璃杯,那么您将需要清洁更多的玻璃杯。
这同样适用于铲子运算符和加号运算符。加上平等的操作员每次需要重新填充玻璃时都会选择一个新的“玻璃”,而铲子操作员只需拿起相同的玻璃并重新填充它。归根结底,为 Plus 等号运算符收集了更多“玻璃”系列。
【讨论】:
很好的比喻,很喜欢。 很好的类比但可怕的结论。您必须补充一点,眼镜是由其他人清洁的,因此您不必关心它们。 很好的类比,我认为它得出了一个很好的结论。我认为这不是关于谁必须清洁玻璃,而是更多关于使用的眼镜数量。您可以想象某些应用程序正在突破其机器上的内存限制,并且这些机器一次只能清洁一定数量的眼镜。【参考方案4】:这是一个老问题,但我刚刚遇到它,我对现有答案并不完全满意。铲子
@noodl 接受的答案表明
一个非常常见的情况是只有一个对字符串的引用。在这种情况下,语义差异无关紧要,自然更喜欢
【讨论】:
【参考方案5】:因为它更快/不会创建字符串的副本垃圾收集器不需要运行。
【讨论】:
虽然上述答案提供了更多细节,但这是唯一将它们放在一起以获得完整答案的答案。这里的关键似乎是你的“建立字符串”的措辞,它暗示你不想要或不需要原始字符串。 这个答案是基于一个错误的前提:分配和释放短期对象在任何中等体面的现代 GC 中基本上都是免费的。它至少与 C 中的堆栈分配一样快,并且比malloc
/ free
快得多。此外,一些更现代的 Ruby 实现可能会完全优化对象分配和字符串连接。 OTOH,mutating 对象对于 GC 性能来说很糟糕。【参考方案6】:
虽然大多数答案都涵盖+=
,因为它会创建一个新副本,因此速度较慢,但请务必记住+=
和<<
不可互换!你想在不同的情况下使用它们。
使用<<
也会改变任何指向b
的变量。在这里,我们还可以在我们不想的时候对a
进行变异。
2.3.1 :001 > a = "hello"
=> "hello"
2.3.1 :002 > b = a
=> "hello"
2.3.1 :003 > b << " world"
=> "hello world"
2.3.1 :004 > a
=> "hello world"
因为+=
会创建一个新副本,所以它还会保留所有指向它的变量不变。
2.3.1 :001 > a = "hello"
=> "hello"
2.3.1 :002 > b = a
=> "hello"
2.3.1 :003 > b += " world"
=> "hello world"
2.3.1 :004 > a
=> "hello"
了解这种区别可以在处理循环时为您省去很多麻烦!
【讨论】:
【参考方案7】:虽然不能直接回答您的问题,但为什么 The Fully Upturned Bin 一直是我最喜欢的 Ruby 文章之一。它还包含一些关于垃圾回收的字符串信息。
【讨论】:
感谢您的提示,迈克尔!我在 Ruby 方面还没有走那么远,但它肯定会在未来派上用场。以上是关于为啥在 Ruby 中构建字符串时,铲子运算符 (<<) 优于加号 (+=)?的主要内容,如果未能解决你的问题,请参考以下文章