HashMap工做原理(转载)

转载自:http://www.importnew.com/7099.html 
html

HashMap的工做原理是近年来常见的Java面试题。几乎每一个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别, 那么为什么这道面试题如此特殊呢?是由于这道题考察的深度很深。这题常常出如今高级或中高级面试中。投资银行更喜欢问这个问题,甚至会要求你实现 HashMap来考察你的编程能力。ConcurrentHashMap和其它同步集合的引入让这道题变得更加复杂。让咱们开始探索的旅程吧!java

先来些简单的问题

“你用过HashMap吗?” “什么是HashMap?你为何用到它?”node

几乎每一个人都会回答“是的”,而后回答HashMap的一些特性,譬如HashMap能够接受null键值和值,而Hashtable则不 能;HashMap是非synchronized;HashMap很快;以及HashMap储存的是键值对等等。这显示出你已经用过HashMap,并且 对它至关的熟悉。可是面试官来个急转直下,今后刻开始问出一些刁钻的问题,关于HashMap的更多基础的细节。面试官可能会问出下面的问题:git

“你知道HashMap的工做原理吗?” “你知道HashMap的get()方法的工做原理吗?”程序员

你也许会回答“我没有详查标准的Java API,你能够看看Java源代码或者Open JDK。”“我能够用Google找到答案。”github

但一些面试者可能能够给出答案,“HashMap是基于hashing的原理,咱们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当咱们给put()方法传递键和值时,咱们先对键调用 hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。”这里关键点在于指出,HashMap是在 bucket中储存键对象和值对象,做为Map.Entry。这一点有助于理解获取对象的逻辑。若是你没有意识到这一点,或者错误的认为仅仅只在 bucket中存储值的话,你将不会回答如何从HashMap中获取对象的逻辑。这个答案至关的正确,也显示出面试者确实知道hashing以及 HashMap的工做原理。可是这仅仅是故事的开始,当面试官加入一些Java程序员天天要碰到的实际场景的时候,错误的答案频现。下个问题多是关于 HashMap中的碰撞探测(collision detection)以及碰撞的解决方法:面试

“当两个对象的hashcode相同会发生什么?” 从这里开始,真正的困惑开始了,一些面试者会回答由于hashcode相同,因此两个对象是相等的,HashMap将会抛出异常,或者不会存储它们。而后 面试官可能会提醒他们有equals()和hashCode()两个方法,并告诉他们两个对象就算hashcode相同,可是它们可能并不相等。一些面试 者可能就此放弃,而另一些还能继续挺进,他们回答“由于hashcode相同,因此它们的bucket位置相同,‘碰撞’会发生。由于HashMap使 用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。”这个答案很是的合理,虽然有不少种处理碰撞的方法,这种方法 是最简单的,也正是HashMap的处理方法。但故事尚未完结,面试官会继续问:编程

“若是两个键的hashcode相同,你如何获取值对象?” 面试者会回答:当咱们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,而后获取值对象。面试官提醒他若是有两个 值对象储存在同一个bucket,他给出答案:将会遍历链表直到找到值对象。面试官会问由于你并无值对象去比较,你是如何肯定肯定找到值对象的?除非面 试者直到HashMap在链表中存储的是键值对,不然他们不可能回答出这一题。数组

其中一些记得这个重要知识点的面试者会说,找到bucket位置以后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。完美的答案!缓存

许多状况下,面试者会在这个环节中出错,由于他们混淆了hashCode()和equals()方法。由于在此以前hashCode()屡屡出现, 而equals()方法仅仅在获取值对象的时候才出现。一些优秀的开发者会指出使用不可变的、声明做final的对象,而且采用合适的equals()和 hashCode()方法的话,将会减小碰撞的发生,提升效率。不可变性使得可以缓存不一样键的hashcode,这将提升整个获取对象的速度,使用 String,Interger这样的wrapper类做为键是很是好的选择。

若是你认为到这里已经完结了,那么听到下面这个问题的时候,你会大吃一惊。“若是HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?”除 非你真正知道HashMap的工做原理,不然你将回答不出这道题。默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时 候,和其它集合类(如ArrayList等)同样,将会建立原来HashMap大小的两倍的bucket数组,来从新调整map的大小,并将原来的对象放 入新的bucket数组中。这个过程叫做rehashing,由于它调用hash方法找到新的bucket位置。

若是你可以回答这道问题,下面的问题来了:“你了解从新调整HashMap大小存在什么问题吗?”你可能回答不上来,这时面试官会提醒你当多线程的状况下,可能产生条件竞争(race condition)。

当从新调整HashMap大小的时候,确实存在条件竞争,由于若是两个线程都发现HashMap须要从新调整大小了,它们会同时试着调整大小。在调 整大小的过程当中,存储在链表中的元素的次序会反过来,由于移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部, 这是为了不尾部遍历(tail traversing)。若是条件竞争发生了,那么就死循环了。这个时候,你能够质问面试官,为何这么奇怪,要在多线程的环境下使用HashMap 呢?:)

热心的读者贡献了更多的关于HashMap的问题:

  1. 为何String, Interger这样的wrapper类适合做为键? String, Interger这样的wrapper类做为HashMap的键是再适合不过了,并且String最为经常使用。由于String是不可变的,也是final 的,并且已经重写了equals()和hashCode()方法了。其余的wrapper类也有这个特色。不可变性是必要的,由于为了要计算 hashCode(),就要防止键值改变,若是键值在放入时和获取时返回不一样的hashcode的话,那么就不能从HashMap中找到你想要的对象。不 可变性还有其余的优势如线程安全。若是你能够仅仅经过将某个field声明成final就能保证hashCode是不变的,那么请这么作吧。由于获取对象 的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是很是重要的。若是两个不相等的对象返回不一样的 hashcode的话,那么碰撞的概率就会小些,这样就能提升HashMap的性能。
  2. 咱们可使用自定义的对象做为键吗? 这是前一个问题的延伸。固然你可能使用任何对象做为键,只要它遵照了equals()和hashCode()方法的定义规则,而且当对象插入到Map中之 后将不会再改变了。若是这个自定义对象时不可变的,那么它已经知足了做为键的条件,由于当它建立以后就已经不能改变了。
  3. 咱们可使用CocurrentHashMap来代替Hashtable吗?这是另一个很热门的面试题,因 为ConcurrentHashMap愈来愈多人用了。咱们知道Hashtable是synchronized的,可是 ConcurrentHashMap同步性能更好,由于它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap固然能够代替 HashTable,可是HashTable提供更强的线程安全性。看看这篇博客查看Hashtable和ConcurrentHashMap的区别。

我我的很喜欢这个问题,由于这个问题的深度和广度,也不直接的涉及到不一样的概念。让咱们再来看看这些问题设计哪些知识点:

  • hashing的概念
  • HashMap中解决碰撞的方法
  • equals()和hashCode()的应用,以及它们在HashMap中的重要性
  • 不可变对象的好处
  • HashMap多线程的条件竞争
  • 从新调整HashMap的大小

总结

HashMap的工做原理

HashMap基于hashing原理,咱们经过put()和get()方法储存和获取对象。当咱们将键值对传递给put()方法时,它调用键对象 的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,经过键对象的equals()方法找到正确的 键值对,而后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每一个链表节点中储存键值对对象。

当两个不一样的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。

由于HashMap的好处很是多,我曾经在电子商务的应用中使用HashMap做为缓存。由于金融领域很是多的运用Java,也出于性能的考虑,咱们会常常用到HashMap和ConcurrentHashMap。

 

 

 

What will happen if two different HashMap  key objects have same hashcode?
They will be stored in same bucket but no next node of linked list. And keys equals () method will be used to identify correct key value pair in HashMap .


How null key is handled in HashMap? Since equals() and hashCode() are used to store and retrieve values, how does it work in case of null key?
Null key is handled specially in HashMap, there are two separate method for that putForNullKey(V value) and getForNullKey(). Later is offloaded version of get() to look up null keys.  Null keys always map to index 0.  This null case is split out into separate methods for the sake of performance in the two most commonly used operations (get and put), but incorporated with conditionals in others. In short, equals() and hashcode() method are not used in case of null keys in HashMap.

here is how nulls are retreived from HashMap

    private V getForNullKey () {
        if (size == 0) {
            return null;
        }
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }

Read more: http://javarevisited.blogspot.com/2011/02/how-hashmap-works-in-java.html#ixzz3PPizhRjZ
相关文章
相关标签/搜索