【问题标题】:Why use symbols as hash keys in Ruby?为什么在 Ruby 中使用符号作为哈希键?
【发布时间】:2012-01-01 15:07:09
【问题描述】:

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

与使用字符串相比有什么优势?

例如:

hash[:name]

对比

hash['name']

【问题讨论】:

    标签: ruby string hashmap symbols


    【解决方案1】:

    TL;DR:

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

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

    简短的回答:

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

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

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

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

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

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

    注意事项:

    如果您进行字符串比较,Ruby 可以仅通过比较它们的对象 id 来比较符号,而无需评估它们。这比比较需要评估的字符串快得多。

    如果您访问散列,Ruby 总是应用散列函数从您使用的任何键计算“散列键”。您可以想象像 MD5 哈希这样的东西。然后 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/

    【讨论】:

    • 仅供参考,Symbols 将在 Ruby 的下一个版本中进行 GCd:bugs.ruby-lang.org/issues/9634
    • 此外,在 Ruby 中用作哈希键时,字符串会自动冻结。因此,在这种情况下谈论字符串时,字符串是可变的并不完全正确。
    • 对主题的深刻见解和“长答案”部分中的第一个链接已被删除或迁移。
    • 符号在 Ruby 2.2 中被垃圾回收
    • 很好的答案!在拖钓方面,您的“简短回答”也足够长。 ;)
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      原因是效率,比字符串有多重收益:

      1. 符号是不可变的,所以问题是“如果密钥更改会发生什么?”不需要问。
      2. 字符串在您的代码中重复,通常会占用更多内存空间。
      3. 散列查找必须计算键的散列来比较它们。这是 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 中计算符号的哈希值并不需要任何时间,因为直接使用了对象 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:确实,这就是我写答案的原因 :-) 我刚刚编辑了我的答案,提到了 Ruby 1.9 中的特殊语法和 Ruby 2.0 承诺的命名参数
      • 你能解释一下哈希查找对于符号是恒定的,对于字符串是 O(n) 吗?
      【解决方案4】:

      Re: 比使用字符串有什么好处?

      • 样式:Ruby 方式
      • (非常)更快的值查找,因为散列符号相当于散列整数与散列字符串。

      • 缺点:占用程序符号表中的一个永远不会释放的槽。

      【讨论】:

      • +1 表示该符号永远不会被垃圾回收。
      • 符号永远不会被垃圾回收 - 从 ruby​​ 2.2+ 开始就不是这样
      猜你喜欢
      • 2011-07-01
      • 1970-01-01
      • 2017-03-23
      • 1970-01-01
      • 2012-07-08
      • 1970-01-01
      • 2018-02-19
      • 2020-07-02
      • 2016-10-23
      相关资源
      最近更新 更多