【问题标题】:How does a hash (in a language like Ruby) work "under the hood"?哈希(在像 Ruby 这样的语言中)如何“在后台”工作?
【发布时间】:2015-09-03 09:27:37
【问题描述】:

我在这里和那里阅读过有关哈希映射/表的内容,并且可以理解哈希表本质上是一个有限大小数组的概念。该函数可以使用模运算符来确定数组中的哪个索引对应于特定的键。如果发生冲突,则可以实现一个链表来存储所有的冲突值。这是我非常新手的理解,我希望有人可以在 Ruby 哈希的上下文中对其进行解释/纠正。在 Ruby 中,你真正需要做的就是

hash = {}    
hash[key] = value

这将创建一个具有相应值的键。假设您只是将一堆符号存储为键,将数字存储为值:

hash[:a] = 1
hash[:b] = 2
...

在将值存储在数组和链表中方面到底发生了什么?什么是碰撞的例子?

【问题讨论】:

标签: ruby hash hashmap hashtable


【解决方案1】:

Ruby 语言规范没有为Hash 类规定任何特定的实现策略。只要遵守合同,每个实现都可以按照自己的意愿实现。

例如,这里是 Rubinius 的实现,它是用 Ruby 编写的,很容易理解:kernel/common/hash.rb 这是一个相当传统的哈希表。 (关于此实现的另一件很酷的事情是,它实际上恰好与 YARV 一样快,这证明 Ruby 代码可以与手动优化的 C 一样高效。)

Rubinius 还可以使用 Hash Array Mapped Trie 实现 Hash 类:kernel/common/hash_hamt.rb [注意:此实现使用三个用 C++ 编写的 VM 原语。]

您可以使用配置选项在这两种实现之间切换。因此,不仅不同的 Ruby 实现之间的Hash 实现不同,甚至在完全相同的 Ruby 实现的完全相同版本的完全相同的程序的两次运行之间也可能不同!

在 IronRuby 中,Ruby 的 Hash 类简单地委托给 .NET System.Collections.Generic.Dictionary<object, object>: Ruby/Builtins/Hash.cs 在以前的版本中,它甚至没有委托,它只是一个子类:Ruby/Builtins/Hash.cs

【讨论】:

    【解决方案2】:

    如果您对此很执着,可以直接查看实现。这是哈希最终使用的内容: https://github.com/ruby/ruby/blob/c8b3f1b470e343e7408ab5883f046b1056d94ccc/st.c

    哈希本身在这里: https://github.com/ruby/ruby/blob/trunk/hash.c

    大多数时候,cmets中提供的文章diego就绰绰有余了

    【讨论】:

      【解决方案3】:

      在 ruby​​ 2.4 中,Hash table 被移动到了开放寻址模型,所以我将只描述 Hash-tables 结构是如何工作的,而不是在 2.4 及更高版本中是如何实现的。

      假设我们将所有条目存储在一个数组中。当我们想要找到某个东西时,我们必须遍历所有元素来匹配一个。如果我们有很多元素,这可能需要很长时间,并且使用哈希表可以让我们通过计算该键的哈希函数直接进入具有所需值的单元格。

      哈希表以类似于数组的数据结构存储存储(bins)组中的所有值。

      How does hash table work

      当我们添加一个新的键值对时,我们需要计算这对将插入到哪个“存储”中,我们使用.hash 方法(哈希函数)来执行此操作。哈希函数的结果值是一个伪随机数,因为它总是为相同的值生成相同的数字。

      粗略地说,hash 返回相当于指向内存位置的链接 当前对象被存储。但是,对于字符串,计算是相对于值而言的。

      收到一个伪随机数后,我们必须计算将存储键值对的“存储”的数量。

      'a'.hash % 16 =>9 a - key 16 - amount of storage 9 - the storage number

      因此,在 Ruby 中,插入的工作方式如下:

      How insertion works

      1. 它使用内部散列函数获取密钥的散列值。 :c.hash #=> 2782

      2. 得到哈希值后,借助模运算(2782 % 16),我们将得到保存我们的键值对的存储编号:d.hash % 16

      3. 将 key-value 添加到适当 bin 的链表中

      搜索工作如下:

      搜索的工作方式完全相同:

      1. 确定“哈希”函数;
      2. 找到“存储”;
      3. 然后遍历列表并检索哈希元素。

      在 ruby​​ 中,每个 bin 的平均元素数为 5。随着记录数的增加,每个存储库中的元素密度会增加(实际上,哈希表的大小仅为 16 个存储)。

      如果元素的密度很大,例如一个“存储”中有10_000个元素,我们将不得不遍历这个链表的所有元素来找到对应的记录。我们会回到 O(n) 时间,这很糟糕。

      为避免这种情况,应用了表重新哈希。这意味着哈希表的大小将增加(直到下一个数字 - 16、32、64、128,...),并且对于所有当前元素,将重新计算“存储”中的位置。

      当所有元素的数量大于最大密度乘以当前表大小时,会发生“重新散列”。

      81 > 5 * 16 - rehash will be called when we add 81 elements to the table.

      num_entries > ST_DEFAULT_MAX_DENSITY * table->num_bins

      当条目数达到当前哈希表的最大可能值时,该哈希表中的“存储”数量会增加(它从 16、32、64、128 中获取下一个大小数),并且它重新计算并更正该哈希中所有条目的位置。

      查看这篇文章以获得更深入的解释:Do You Know How Hash Table Works? (Ruby Examples)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-23
        • 1970-01-01
        • 2012-03-09
        • 2015-11-10
        • 2014-10-25
        • 1970-01-01
        相关资源
        最近更新 更多