为啥在 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

所以&lt;&lt; 更改了原始字符串,而不是创建一个新字符串。这样做的原因是,在 ruby​​ 中,a += ba = a + b 的语法简写(其他 &lt;op&gt;= 运算符也是如此),这是一个赋值。另一方面,&lt;&lt;concat() 的别名,它会就地改变接收器。

【讨论】:

谢谢,面条!那么,从本质上讲, This benchmark 表示Array#join 比使用&lt;&lt; 慢。 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】:

虽然大多数答案都涵盖+=,因为它会创建一个新副本,因此速度较慢,但​​请务必记住+=&lt;&lt; 不可互换!你想在不同的情况下使用它们。

使用&lt;&lt; 也会改变任何指向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 中构建字符串时,铲子运算符 (<<) 优于加号 (+=)?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Ruby 象征着我的哈希键?

<<运算符将字符串添加到列表代理奇怪 - Ruby

为啥我不能解析保存到 Ruby 中变量的日期字符串?

为啥 C 在使用条件运算符时不允许连接字符串?

为啥在ruby中,[].all?i i==1 的结果为true?

如何在 Ruby 中将字符串或整数转换为二进制?