为什么在 Ruby 中使用符号作为散列键?

很多时候,人们在 Ruby 散列中使用符号作为键。

使用字符串有什么好处?

例如:

hash[:name]

对。

hash['name']
65184 次浏览

译者:

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

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

简短的回答:

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

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

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

如果你使用一个字符串作为哈希键,Ruby 需要计算这个字符串并查看它的内容(并计算一个哈希函数) ,然后将结果与已经存储在哈希中的键的(哈希)值进行比较。

如果你使用一个符号作为一个 Hash 键,那么它是不可变的,所以 Ruby 基本上只需要比较一下(Hash 函数的) object-id 和(Hash) object-ids 中已经存储在 Hash 中的键。(更快)

缺点: 每个符号消耗 Ruby 解释器符号表中的一个槽,这个槽永远不会被释放。 符号永远不会被垃圾收集。 所以当你有大量的符号(例如自动生成的符号)时,就是一个极端的情况。在这种情况下,您应该评估这对 Ruby 解释器大小的影响。

备注:

如果进行字符串比较,Ruby 可以通过比较符号的对象 id 来进行比较,而不必计算它们。这比比较需要计算的字符串要快得多。

如果您访问一个 hash,Ruby 总是应用一个 hash 函数从您使用的任何键计算一个“ hash-key”。您可以想象类似 MD5-hash 的东西。然后 Ruby 将这些“散列键”进行对比。

每次在代码中使用字符串时,都会创建一个新实例——字符串创建比引用符号慢。

从 Ruby 2.1开始,当您使用冻结字符串时,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/

Https://www.rubyguides.com/2016/01/ruby-mutability/

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

  • 造型: 红宝石风格
  • (非常)稍微快一点的值查找,因为散列一个符号相当于散列一个整数与散列一个字符串。

  • 缺点: 消耗程序符号表中永远不会释放的槽。

原因在于效率,比 String 有多重收益:

  1. 符号是不可变的,因此不需要问“如果键发生变化会发生什么”这个问题。
  2. 字符串在代码中被复制,通常会占用更多的内存空间。
  3. 散列查找必须计算键的散列以比较它们。这是字符串的 O(n)和符号的常量。

此外,Ruby 1.9引入了一种简化的语法,只适用于带有符号键的 hash (例如 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)字母“ b”、“ a”和“ r”对于程序中所有 :bar的出现只存储一次。在 Ruby 2.2之前,不断创建从未被重用的新 Symbols是一个糟糕的主意,因为它们将永远保留在全局符号查找表中。Ruby 2.2会垃圾收集它们,所以不用担心。

3)实际上,在 Ruby 1.8. x 中计算一个符号的散列并不需要花费任何时间,因为对象 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

我对 Ruby 2.x 中引入的关于冻结字符串的后续内容非常感兴趣。

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

当你和几十个人打交道,但他们从来没有改变(如果他们是你的业务“词汇”) ,我喜欢认为冻结他们可以有所不同。我还没有做任何基准测试,但我想它会接近符号的性能。