为什么在Ruby中构建字符串时,铲运算符(<<)优于正等号(+ =)?


156

我正在研究Ruby Koans。

about_strings.rb中test_the_shovel_operator_modifies_the_original_stringKoan 包含以下注释:

在构建字符串时,Ruby程序员倾向于使用铲运算符(<<)而不是加等于运算符(+ =)。为什么?

我的猜测是它涉及速度,但我不了解引擎盖下的动作会导致铲子操作员更快。

有人可以解释此偏好背后的详细信息吗?


4
铲操作符修改String对象,而不是创建一个新的String对象(消耗内存)。语法不好吗?cf. Java和.NET有StringBuilder的类
上校恐慌

Answers:


257

证明:

a = 'foo'
a.object_id #=> 2154889340
a << 'bar'
a.object_id #=> 2154889340
a += 'quux'
a.object_id #=> 2154742560

因此,<<更改原始字符串而不是创建新字符串。这是因为在ruby中,ruby a += b是语法赋值(a = a + b其他<op>=运算符也是如此)。另一方面,<<它的别名concat()会改变接收器的位置。


3
谢谢,noodl!那么,从本质上来说,<< <<会更快,因为它不会创建新对象吗?
erinbrown 2011年

1
该基准测试表明,Array#join比使用慢<<
Andrew Grimm

5
EdgeCase的其中一位成员发布了有关性能数字的解释:有关字符串的更多信息
Cincinnati Joe

8
上面的@CincinnatiJoe链接似乎已断开,这是一个新链接:有关字符串的更多信息
jasoares 2012年

对于Java人士:Ruby中的'+'运算符对应于通过StringBuilder对象追加,而'<<'对应于String对象的串联
nanosoft

79

性能证明:

#!/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)

70

一个正在学习Ruby作为他的第一门编程语言的朋友,在Ruby Koans系列上的Ruby中学习Strings时问了我同样的问题。我用以下类比向他解释了这一点:

您的一杯水已满,并且需要重新装满一杯。

第一种方法是,拿起新杯子,用自来水将水倒入一半,然后再用第二半满的杯子重新装满水杯。每当您需要重新装满玻璃杯时,都可以这样做。

第二种方法是将半满的玻璃杯装满水,然后直接从水龙头中重新装满水。

在一天结束时,如果您每次需要补充玻璃杯时都选择摘新玻璃杯,那么您将需要清洁更多的玻璃杯。

铲子运算符和加等于运算符也是如此。另外,equal操作员每次需要重新装满玻璃时都会选择一个新的“玻璃杯”,而铲子操作员只需取同样的玻璃杯并重新装满。最终,Plus equal操作员将获得更多“眼镜”收藏。


2
打个比方,喜欢它。
GMA 2013年

5
很好的类比,但结论很糟糕。您将不得不补充说,眼镜是由其他人清洁的,因此您不必关心它们。
Filip Bartuzi

1
很好的类比,我认为可以得出一个很好的结论。我认为这与谁必须清洁玻璃有关,而与所使用的玻璃数量无关。您可以想象某些应用程序正在限制其计算机上的内存限制,并且这些计算机一次只能清洁一定数量的眼镜。
查理L

11

这是一个老问题,但我只是碰到了这个问题,对现有答案并不完全满意。铁锹<<比串联+ =快有很多好处,但也有语义上的考虑。

@noodl接受的答案表明<< <<修改了现有对象,而+ =创建了一个新对象。因此,您需要考虑是要让对字符串的所有引用都反映新值,还是要让现有引用不留任何空间,而是创建一个新的字符串值以供本地使用。如果需要所有引用来反映更新的值,则需要使用<<。如果您想不理会其他引用,则需要使用+ =。

一个非常常见的情况是仅对字符串的单个引用。在这种情况下,语义差异并不重要,由于其速度快,因此自然优先选择<<。


10

因为它更快/不会创建字符串的副本,所以<->垃圾收集器不需要运行。


虽然以上答案提供了更多详细信息,但这是将它们组合在一起以获得完整答案的唯一方法。这里的关键似乎是措辞上的“建立字符串”,这意味着您不需要或不需要原始字符串。
德鲁·弗里

这个答案基于一个错误的前提:分配和释放短期对象在任何半途而废的现代GC中都是免费的。它至少与C中的堆栈分配一样快,并且比malloc/ 快得多free。而且,一些更现代的Ruby实现可能会完全优化对象分配和字符串连接。OTOH,变异对象对于GC性能来说是很糟糕的。
约尔格W¯¯米塔格

4

尽管大多数答案+=由于创建新副本而较慢,但请记住这一点+=并且<< 不可互换,这一点很重要!您想在不同的情况下使用它们。

使用<<还将更改指向的所有变量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"

理解这种区别可以在处理循环时省去很多麻烦!


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.