为什么在Ruby中将符号用作哈希键?


161

很多时候人们使用符号作为Ruby哈希中的键。

使用字符串有什么好处?

例如:

hash[:name]

hash['name']

Answers:


226

TL; DR:

使用符号不仅可以节省进行比较的时间,还可以节省内存,因为它们仅存储一次。

Ruby符号是不可变的(无法更改),这使查找内容变得更加容易

简短答案:

使用符号不仅可以节省进行比较的时间,还可以节省内存,因为它们仅存储一次。

Ruby中的符号基本上是“不可变的字符串” ..这意味着它们无法更改,并且意味着在源代码中多次引用相同的符号时,该符号始终存储为相同的实体,例如具有相同的对象ID 。

另一方面字符串是可变的,可以随时更改。这意味着Ruby需要将您在整个源代码中提到的每个字符串存储在单独的实体中,例如,如果您在源代码中多次提到字符串“ name”,则Ruby需要将所有这些都存储在单独的String对象中,因为它们稍后可能会更改(这是Ruby字符串的本质)。

如果您使用字符串作为哈希键,则Ruby需要评估该字符串并查看其内容(并在其上计算哈希函数),然后将结果与哈希中已存储的键的(哈希)值进行比较。

如果使用符号作为哈希键,则隐含它是不可变的,因此Ruby基本上可以将对象ID的(哈希函数)与已经存储在其中的键的(哈希)对象ID进行比较。哈希。(快多了)

缺点: 每个符号都占用Ruby解释器的符号表中的一个插槽,该插槽从不释放。符号永远不会被垃圾收集。因此,当您有大量符号(例如,自动生成的符号)时,便是一个极端情况。在这种情况下,您应该评估这如何影响Ruby解释器的大小。

笔记:

如果进行字符串比较,Ruby可以仅通过符号的对象ID来比较符号,而不必对其进行评估。这比比较需要评估的字符串要快得多。

如果您访问哈希,Ruby始终会应用哈希函数来根据您使用的任何密钥来计算“哈希密钥”。您可以想象像MD5哈希这样的东西。然后Ruby将这些“哈希键”相互比较。

长答案:

https://web.archive.org/web/20180709094450/http://www.reactive.io/tips/2009/01/11/the-difference-between-ruby-symbols-and-strings

http://www.randomhacks.net.s3-website-us-east-1.amazonaws.com/2007/01/20/13-ways-of-looking-at-a-ruby-symbol/


5
Fyi,Symbols将在下一个版本的Ruby中进行GCd处理
Ajedi32

2
另外,在Ruby中用作Hash键时,字符串会自动冻结。因此,在这种情况下谈论字符串时,可变字符串并不是完全正确的。
2014年

1
对主题的深入了解和“长期解答”部分中的第一个链接已删除或迁移。
Hbksagar 2014年

2
符号垃圾收集红宝石2.2
马克-安德烈·Lafortune

2
好答案!在拖钓方面,您的“简短答案”也足够长。;)
technophyle

22

原因是效率,在String上有多个收益:

  1. 符号是不可变的,因此问题“如果密钥更改会发生什么?” 不需要问。
  2. 字符串在您的代码中重复,通常会占用更多的内存空间。
  3. 散列查找必须计算键的散列以进行比较。这O(n)用于字符串,常量用于符号。

而且,Ruby 1.9引入了一种简化的语法,仅用于带有符号键的哈希(例如h.merge(foo: 42, bar: 6)),而Ruby 2.0的关键字参数仅适用于符号键。

注意事项

1)当您发现Ruby String与其他类型的键区别对待时,您可能会感到惊讶。确实:

s = "foo"
h = {}
h[s] = "bar"
s.upcase!
h.rehash   # must be called whenever a key changes!
h[s]   # => nil, not "bar"
h.keys
h.keys.first.upcase!  # => TypeError: can't modify frozen string

仅对于字符串键,Ruby将使用冻结副本而不是对象本身。

2)对于:bar程序中所有出现的字母“ b”,“ a”和“ r”仅存储一次。在Ruby 2.2之前,不断创建Symbols从未重用的新代码是一个坏主意,因为它们将永远保留在全局Symbol查找表中。Ruby 2.2会垃圾回收它们,所以不用担心。

3)实际上,在Ruby 1.8.x中无需花费任何时间计算Symbol的哈希值,因为直接使用了对象ID:

:bar.object_id == :bar.hash # => true in Ruby 1.8.7

在Ruby 1.9.x中,随着哈希值从一个会话更改为另一个会话(包括的会话),这种情况已经改变Symbols

:bar.hash # => some number that will be different next time Ruby 1.9 is ran

为您的精彩笔记+1!我最初在回答中没有提到哈希函数,因为我试图使其更易于阅读:)
Tilo

@Tilo:的确,这就是为什么我写我的回答:-)我只是修改我的答案提Ruby 1.9中的特殊语法和Ruby 2.0的承诺命名参数
马克-安德烈·Lafortune

您能解释一下符号的哈希查询常量和字符串的O(n)常量吗?
阿萨德·穆斯维

7

回复:使用字符串有什么好处?

  • 样式:Ruby之道
  • (非常)略微加快了值查找的速度,因为哈希符号等效于哈希整数与哈希字符串。

  • 缺点:占用了程序符号表中从未释放的插槽。


4
+1表示该符号从未被垃圾回收。
Vortico

符号永远不会被垃圾收集-因为红宝石2.2+不是真的
eudaimonia

0

我对Ruby 2.x中引入的冻结字符串的后续工作非常感兴趣。

当您处理来自文本输入的大量字符串(例如,我正在考虑通过Rack通过HTTP的HTTP参数或有效负载)时,在任何地方使用字符串都更加容易。

当您处理数十种方法但它们从未改变时(如果它们是您的业务“词汇”),我想认为冻结它们可以有所作为。我还没有做过任何基准测试,但是我想它将接近符号的性能。

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.