hashmap 或 hashtable 中的重新散列过程

Posted

技术标签:

【中文标题】hashmap 或 hashtable 中的重新散列过程【英文标题】:Rehashing process in hashmap or hashtable 【发布时间】:2012-05-26 04:22:07 【问题描述】:

当大小超过 maxthreshold 值时,如何在 hashmap 或 hashtable 中完成重新散列过程?

是否所有对都只是复制到一个新的存储桶数组中?

编辑:

重新散列后,同一个桶(链表中)的元素会发生什么?我的意思是他们会在重新散列后留在同一个桶中吗?

【问题讨论】:

grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… 当您重新哈希并将所有内容移动到新位置(存储桶等)时,旧元素也会再次重新哈希并根据其新哈希码存储在新存储桶中。分配用于存储元素的旧空间被垃圾回收。 @dharam:因此得出的结论是,在同一个桶中的元素在重新哈希后可能不在同一个桶中,不在同一个桶中的元素在重新哈希后可能在同一个桶中? 是的..你是绝对正确的:) 询问“这通常是如何根据教科书完成的”并不等同于“在这个特定的实现中这是如何完成的”。例如,Java 对少于 8 个条目的存储桶使用链表节点,而对具有 8 个或更多条目的存储桶使用树状节点。 【参考方案1】:

问题中的最大阈值称为负载因子。

建议负载因子在 0.75 左右。负载因子定义为 (m/n),其中 n 是哈希表的总大小,m 是在需要增加基础数据结构大小之前可以插入的首选条目数。

可以在两种情况下进行重新散列:

    当当前 m'/n 比增加超过负载系数时

    M'/n 比率下降到非常低的值,例如 0.1

在这两种情况下,m' 都是当前条目数。此外,这两种情况都需要将当前条目转移到更大或更小的哈希表中。

在问题的上下文中,重新散列是对条目应用散列函数以将它们移动到另一个散列表的过程。可以使用之前使用的哈希函数,也可以完全使用新函数。

请注意:当发生碰撞时,也会进行重新散列。 (这也是一种处理碰撞的方式。)

要添加更多上下文和详细讨论,请访问我的博客Hashing Basics

【讨论】:

“可能使用相同的哈希函数”:当桶数增加时,如何使用新的桶? 我相信新的存储桶(和重新散列的表)的使用与原始哈希表没有什么不同,这意味着在重新散列后它将像往常一样使用更多的存储桶。 您掌握了精髓。在地图的高级实现中,重新散列是一个复杂的过程。它在一段时间内完成,其中涉及多个线程以减少每个线程的摊销成本。因此,释放原始数组的空间需要合理的时间。直到那时,这两个数组都存在并且功能齐全:) @dharam "请注意:发生碰撞时也会进行重新散列。"在这种情况下,阈值是多少?如何统计bucket中允许的最大元素个数?【参考方案2】:

当映射中的元素数量达到最大阈值时,对哈希映射进行重新散列。

通常负载因子值为0.75,默认初始容量值为16。一旦元素数量达到或超过容量的0.75倍,则对map进行重新散列。在这种情况下,当元素数量为 12 时,就会发生重新散列。 (0.75 * 16 = 12)

当重新散列发生时,可以使用新的散列函数,甚至可以使用相同的散列函数,但存在值的存储桶可能会发生变化。基本上,当重新散列发生时,桶的数量大约翻了一番,因此必须将值放入的新索引发生变化。

在重新散列时,每个桶的链表按顺序颠倒。发生这种情况是因为 HashMap 没有在尾部附加新元素,而是在头部附加新元素。因此,当重新散列发生时,它读取每个元素并将其插入到头部的新存储桶中,然后继续在新映射的头部添加来自旧映射的下一个元素,从而导致链表反转。

如果有多个线程处理同一个哈希映射,可能会导致无限循环。

可以在此处找到说明在上述情况下如何发生无限循环的详细说明:http://mailinator.blogspot.hu/2009/06/beautiful-race-condition.html

如果插入到地图中的元素必须按键排序,则可以使用 TreeMap。但是如果键的顺序无关紧要,HashMap 会更有效。

【讨论】:

我唯一的问题是,如果每 13 个元素都会发生一次重新散列,那将是次优的权利。可以说,num_buckets = 16,total_no_entries = 一开始bucket的数量是16个,当遇到第13个元素时会重新hash,bucket的数量会翻倍。这将使桶的总数为 32 并且下一次重新散列发生在超过 0.75 * 32 时,即第 25 个元素。然后桶的数量再次翻倍到 64 并且下一次重新散列将在超过 0.75 * 64 时发生,即第 49 个元素。同样,它继续下去,你可以看到它不会在每 13 个元素后重新散列,而是继续加倍。如果 n 是重新散列发生的点,那么下一次重新散列将在 2*n - 1 处发生。【参考方案3】:

Hashing – Rehashing and Race condition

基本上,在创建哈希映射时,集合会为其分配一个默认容量(2^4,即 16.)。在地图中添加元素的后期阶段以及在接近初始定义容量的某个阶段之后,需要 ReHashing 来保持性能。

为集合定义了LoadFactor(据说好为0.75),这指定了时间和空间的好索引。

更大的负载系数 => 更低的空间消耗但更高的查找次数 较小的负载系数 => 与所需的元素数量相比,空间消耗更大。

Java 规范建议良好的负载因子值为 .75

因此假设您的最大要求是在散列中存储 10 个元素,然后考虑到 Good Loadfactor .75 = 在集合中添加 7 个元素后会发生重新散列。如果您的要求(在这种情况下)不符合 7,那么 Rehashing 将永远不会发生。

如果没有大量元素要存储在hashmap中,那么创建具有足够容量的HashMap总是好的;这比让它执行自动重新散列更有效。

RACE 条件:在对存储在给定存储桶的链表中的内部元素进行重新哈希处理时。他们的顺序相反。假设有两个线程同时遇到竞争条件,那么由于顺序已更改,因此在遍历时有可能第二个线程进入无限循环。

【讨论】:

在您发布答案之前,请检查您的答案在预览窗口中的外观。您缺少几个换行符,您可以通过在前几行的末尾添加两个空格来修复它们。 Hashmap 默认大小为 16

以上是关于hashmap 或 hashtable 中的重新散列过程的主要内容,如果未能解决你的问题,请参考以下文章

Java集合之HashMap与Hashtable的区别

HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别

HashTable —— 线程安全的散列表

Hashtable 和 HashMap 的区别

Java 如何对 HashMap 或 HashTable 中的项目进行排序?

HashMap,HashTable,ConcurrentHashMap的实现原理及区别