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

5hcedyr0  于 2023-01-30  发布在  Ruby
关注(0)|答案(4)|浏览(112)

很多时候,人们使用符号作为Ruby散列中的键。
与使用字符串相比有什么好处?
例如:

hash[:name]

hash['name']
wb1gzix0

wb1gzix01#

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

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

    • 简短的回答:**
    • 使用符号不仅可以节省比较时间,还可以节省内存,因为它们只存储一次。**
    • Ruby中的符号基本上是"不可变的字符串"**..这意味着它们不能被更改,这意味着同一个符号在源代码中被多次引用时,总是存储为同一个实体,例如具有相同的对象ID。
a = 'name'
  a.object_id
=> 557720

  b = 'name'
=> 557740

  'name'.object_id
=> 1373460

  'name'.object_id
=> 1373480          # !! different entity from the one above

# Ruby assumes any string can change at any point in time, 
# therefore treating it as a separate entity

# versus:

  :name.object_id
=> 71068

  :name.object_id
=> 71068

# the symbol :name is a references to the same unique entity
    • 另一方面,字符串是可变的**,它们可以随时改变。这意味着Ruby需要将你在源代码中提到的每个字符串存储在它的单独实体中,例如,如果你在源代码中多次提到字符串"name",Ruby需要将它们存储在单独的String对象中,因为它们可能在以后改变(这是Ruby字符串的本质)。

如果你使用一个字符串作为哈希键,Ruby需要评估这个字符串,查看它的内容(并计算一个哈希函数),然后将结果与已经存储在哈希中的键的(哈希)值进行比较。
如果你使用一个符号作为哈希键,这就意味着它是不可变的,所以Ruby基本上只需要将object-id(的哈希函数)与已经存储在哈希中的键的(哈希的)object-id进行比较(快得多)。

    • 缺点:**每个符号都会占用Ruby解释器的符号表中的一个槽,这个槽永远不会被释放。符号永远不会被垃圾收集。所以极端情况是当你有大量符号的时候(比如自动生成的符号)。在这种情况下,你应该评估这会如何影响你的Ruby解释器的大小(比如,如果你通过编程生成太多的符号,Ruby会耗尽内存并崩溃)。
    • 注:**

如果你做字符串比较,Ruby可以通过比较符号的对象id来比较符号,而不需要对它们求值,这比比较字符串要快得多,因为字符串需要求值。
如果你访问一个散列,Ruby总是应用一个散列函数,从你使用的任何键计算一个"散列键",你可以想象类似MD5散列的东西,然后Ruby将这些"散列键"相互比较。
每次在代码中使用字符串时,都会创建一个新示例-创建字符串比引用符号慢。
从Ruby 2.1开始,当你使用冻结字符串时,Ruby会使用相同的string对象,这避免了必须创建相同字符串的新副本,并且它们被存储在一个垃圾收集的空间中。

    • 详细答案:**

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/

thtygnil

thtygnil2#

原因是效率,在一个字符串上有多个增益:
1.符号是不可变的,所以不需要问“如果键改变会发生什么?”
1.字符串在代码中是重复的,通常会占用更多的内存空间。
1.散列查找必须计算键的散列来比较它们。对于字符串,这是O(n),对于符号,这是常量。
此外,Ruby 1.9引入了一个简化的语法,只用于符号键的哈希(例如h.merge(foo: 42, bar: 6)),Ruby 2.0有keyword arguments,只用于符号键。

  • 备注 *:

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是一个坏主意,因为它们将永远保留在全局符号查找表中。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
332nm8kg

332nm8kg3#

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

  • 造型:Ruby路
  • (非常)稍微快一点的值查找,因为散列符号与散列整数和散列字符串等效。
  • 缺点:使用程序符号表中的一个槽,该槽永远不会释放。
nfg76nw0

nfg76nw04#

我对Ruby 2.x中引入的冻结字符串非常感兴趣。
当您处理来自文本输入的大量字符串时(例如,我考虑的是HTTP参数或有效负载,通过Rack),在任何地方使用字符串都要容易得多。
当你处理几十个符号,但它们从来没有改变过(如果它们是你的业务“词汇”),我想冻结它们可以带来不同。我还没有做任何基准测试,但我猜它会接近符号的性能。

相关问题