在 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
它使用内部散列函数获取密钥的散列值。
:c.hash #=> 2782
得到哈希值后,借助模运算(2782 % 16),我们将得到保存我们的键值对的存储编号:d.hash % 16
将 key-value 添加到适当 bin 的链表中
搜索工作如下:
搜索的工作方式完全相同:
- 确定“哈希”函数;
- 找到“存储”;
- 然后遍历列表并检索哈希元素。
在 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)