HashMap如何真正起作用?

时间:2015-08-22 17:53:48

标签: java

根据这个问题

how-does-a-hashmap-work-in-javathis

许多键值对可以存放在同一个存储桶中(在使用哈希计算存储桶的索引之后),当我们调用get(key)时,它会查看链接列表并使用{{1}进行测试方法。

对我来说听起来并不是真的优化,在使用equals之前是不是要比较链接列表的hashCode

如果答案为否:

这意味着大多数时间桶只包含1个节点,你能解释一下原因吗?因为根据this logical explanation,许多不同的键可以具有相同的桶索引。

如何实现确保密钥的良好分配?这可能意味着存储桶表大小相对于键数

即使表Bucket大小等于键的数量,HashMap equals函数如何确保键的良好分配?不是随机分布?,

我们可以了解更多细节吗?

5 个答案:

答案 0 :(得分:6)

实施是开源的,所以我鼓励您read the code提出任何具体问题。但这是一般的想法:

  • 良好的hashCode分发的主要责任在于键'类,而不是HashMap。如果密钥的hashCode()方法分布不正确(例如,return 0;)然后HashMap将表现不佳。
  • HashMap 做了一些“重新散列”以确保稍微更好的分发,但不多(参见HashMap::hash
  • get方面,对存储桶中的每个元素进行了几次检查(是的,实现为链接列表)
    • 首先,HashMap使用传入密钥的hashCode检查元素的hashCode。这是因为此操作很快,并且元素的hashCode在put时被缓存。这样可以防止具有不同hashCodes的元素(因此通过hashCode的契约和由Object建立的等号不相等)但恰好属于同一个桶(请记住,桶索引基本上是hashCode % buckets.length
    • 如果成功,然后,HashMap会明确检查equals以确保它们真的相等。请记住,相等意味着相同的hashCode,但是相同的hashCode 需要相等(并且不能,因为某些类可能具有无限数量的不同值 - 如String - 但是只有有限数量的可能的hashCode值)

对hashCode和equals进行双重检查的原因是快速和正确。考虑两个具有不同hashCode的键,但最终位于相同的HashMap存储桶中。例如,如果密钥A具有hashCode = 7并且B具有hashCode = 14,并且存在7个桶,则它们都将在桶0(7 % 7 == 014 % 7 == 0)中结束。检查hashCodes有一种快速查看A和B不相等的方法。如果您发现hashCodes相等,那么您可以通过调用equals来确保它不仅仅是hashCode冲突。这只是一个优化,真的;一般的哈希映射算法不需要它。

答案 1 :(得分:1)

为了避免在链表中进行多重比较,HashMap中的桶数通常保持足够大,以至于大多数桶只包含一个项目。默认情况下,java.util.HashMap会尝试维护足够的存储区,以使项目数仅为存储区数量的75%。

某些存储桶可能仍然包含多个项目 - 所谓的“哈希冲突” - 其他存储桶将为空。但平均而言,大多数包含项目的桶只包含一个项目。

equals()方法将始终至少使用一次,以确定密钥是否完全匹配。请注意,equals()方法通常至少与hashCode()方法一样快。

良好的hashCode()实现维护了良好的密钥分发; HashMap对此没什么影响。一个好的hashCode()方法是返回的哈希与对象的值尽可能随机关系的方法。

对于错误哈希函数的示例,曾几何时,String.hashCode()方法仅依赖于字符串的开头。问题在于,有时您希望在HashMap中存储一堆字符串,这些字符串都是相同的 - 例如,单个网站上所有页面的URL - 导致哈希冲突的比例过高。我相信String.hashCode()后来被修改以解决这个问题。

答案 2 :(得分:0)

  

dosn它比较链接列表的hachCodes而不是使用   等于

不需要。 hashcode用于确定放置或获取操作的桶号。一旦你知道带有哈希码的桶号并在那里找到它的链表,那么你知道你需要迭代它并需要检查相等性以找到确切的密钥。所以这里不需要哈希码比较

这就是为什么hashcode应该尽可能唯一,以便最好地进行查找。

  

这意味着大多数时间桶只包含1个节点

不。它取决于hascode的唯一性。如果两个密钥对象具有相同的哈希码但不相等,则桶包含两个节点

答案 3 :(得分:0)

当我们将Key和Value对象传递给Java HashMap上的put()方法时,HashMap实现调用Key对象上的hashCode方法,并将返回的hashcode应用到自己的散列函数中,以找到存储Entry对象的存储桶位置,重要的一点是提及是Java中的HashMap将key和value对象存储在存储桶中Map.Entry,这对理解检索逻辑至关重要。

在检索Key的值时,如果hashcode与其他一些键相同,则bucket位置相同,并且HashMap中会发生冲突,因为HashMap使用LinkedList来存储对象,此条目(Map.Entry的对象包含键)和值)将存储在LinkedList中。

密钥的良好分配将取决于hashcode方法的实施。此实现必须遵守哈希码的一般合同:

  1. 如果两个对象等于equals()方法,则hashCode()方法返回的哈希码必须相同。
  2. 每当在单个执行的应用程序中多次在同一对象上调用hashCode()mehtod时,hashCode()必须返回相同的整数,前提是不会修改equals和hashcode中使用的信息或字段。在多次执行应用程序期间,此整数不需要相同。
  3. 如果两个对象不等于equals()方法,则不需要哈希码必须不同。虽然为不等对象返回不同的hashCode总是很好的做法。不同对象的不同hashCode可以通过减少冲突来提高hashmap或hashtable的性能。

答案 4 :(得分:0)

您可以访问此git-hub存储库“https://github.com/devashish234073/alternate-hash-map-implementation-Java/blob/master/README.md”。

您可以通过基本实现和示例了解HashMap的工作原理。 ReadMe.md解释了所有。

在此处包含示例的某些部分:

假设我必须存储以下键值对。 (KEY1,VAL1) (KEY2,val2)将 (KEY3,VAL3) (....,....) (key99999,val99999)

让我们的哈希算法仅在0到5之间产生值。

首先,我们创建一个带有6个桶的机架,编号为0到5。


存储:

  

存储(keyN,valN):   
1.getget'keyN'的哈希值   
2.ssuppose我们得到2   
3.将(keyN,valN)存储在机架2中

搜索:

  

搜索keyN:   
1.get hash of keyN   
2.lets说我们得到2   
3.我们遍历机架2并获取密钥并返回值

因此,对于N个键,如果我们将它们线性存储,则需要N比较来搜索最后一个元素,但是对于其哈希算法生成25个值的hashmap,我们只需要进行N / 25比较。 [哈希值均匀分散]