HashMap源码分析

来源:互联网 发布:网络p2p的四条红线 编辑:程序博客网 时间:2024/05/16 07:56

HashMap作为最常用的容器类之一不一定每个人都对它非常了解。即使面试题也仅仅会比较几种容器的性能和线程安全问题。

       HashMap如此常用确实有着它精巧的设计,即使我现在也无法真正理解其hash()策略是如何想到的。这里就把我一些理解的写出来,一方面是自己的笔记,另一方面是可以和广大程序员作一个交流,说不定能解开我一些无法解答的迷。

       这里先提几个问题作为引子。

1.       HashMap是以什么为基础结构来作到快速定位。

2.       HashMap是如何解决Key冲突的。

3.       如何保证数组的容量在控制范围内(就是数组的下标是自己能掌控的)

4.       HashMap的散列算法。

5.       HashMap的一些弊端。

       这几个问题如果扩展开来讲都代表着一些思想在里面。好,先从第一个开始。其实我们都知道Map有着Key-Value键值对,这样就可以保证快速定位,这些思想在硬件内存索引,搜索引擎,数据库中都有着类似的具体应用。这里就不展开来讲。下面就是要知道在java的基础类中有哪些可以满足这样的要求,可以直接根据key找到value。可能一时想不到数组,或许很多人没等想出答案已经在不同途径知道答案了。数组(PS:Java中数组并不是一个常用的结构,一般都会用List来代替)。数组正有这样的特性。其下标正是我们想要的key

       这里又会出两个新问题:a)keyInteger类型;b)如何保证key的唯一性。这就是我上面所提出的第二个问题。为了使key成为Integer类型,可以用一定的算法让各种类型映射成Integer类型,其实这里难就难在如何有一个算法让不同类型的值能映射成Integer的时候唯一,至少保证大部分唯一。这里联想到hashCode可以满足这个要求,而且我们也可以实现自己的hashCode方法来达到这个目的。这样第二个问题就解决了。

       这里就会到第三个问题,因为hashCode虽然可以实现唯一,但是下一个问题就出现了,那数组下标如何保证,我们不可能申请一个无限大的内存,而且hashCode是有负数的。这样就引出了HashMap第四个问题,它精巧的hash算法。关于HashMap中的hash()方法是根据位移来保证高位参与^的运算。这是因为当数组申请的容量很小的时候(HashMap默认是16)。很容易造成key冲突。

       这样讲估计很难讲清楚,我先把代码贴出来:

        第一个是它的hash算法,这里通过位移的方式让高位参与运算,很多人会问为什么要这样做,这里就是为了解决key冲突的算法。

[java] view plaincopy
  1. static int hash(int h) {  
  2.     // This function ensures that hashCodes that differ only by  
  3.     // constant multiples at each bit position have a bounded  
  4.     // number of collisions (approximately 8 at default load factor).  
  5.     h ^= (h >>> 20) ^ (h >>> 12);  
  6.     return h ^ (h >>> 7) ^ (h >>> 4);  
  7. }  


       第二个看一下它另外一个精巧的设计,就是如何保证数组key是保证在可控的范围内。这个方法在我第一次看到的时候真心感觉这个设计太精巧了。

[java] view plaincopy
  1. /** 
  2.  * Returns index for hash code h. 
  3.  */  
  4. static int indexFor(int h, int length) {  
  5.     return h & (length-1);  
  6. }  

         

        这段代码是put()中的一段,就是用来判断是否有相同值,如果hashCode和key都相同,说明确实相同。极端情况下要把余下的数组扫描完。所以说一个好的哈希算法的重要性。

[java] view plaincopy
  1. for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  2.     Object k;  
  3.     if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  4.         V oldValue = e.value;  
  5.         e.value = value;  
  6.         e.recordAccess(this);  
  7.         return oldValue;  
  8.     }  
  9. }  


 

      这两个方法其实要合并起来看,第一个方法是保证第二个方法不至于过多的key冲突。这里举个例子,如果HashMap的容量是16,那么(length-1)就是15,二进制是1111,这样它可以存储key从0~15的值,比如我要存储下标为1这个值,这样不通过hash算法将会出现大量的key冲突,比如说二进制0001,10001,110001这样只要低四位是0001的hashCode都可以满足这样要求。hash算法通过高位右移来参与低位的运算以求保证低位的不同。至于具体为什么可以达到这样的目标我也说不上来,看了半天也无法说出个理来。即使这样也不能保证百分之百的不冲突,只是概率会降低很多。

       好,第三个问题就到此为止。

       第四个问题。关于HashMap的一些弊端。第一个就是对于数组利用率,由于hashCode接近于随机性,导致对于数组的利用率并不能到达100%。大概只有70%左右。第二个就是当删除数据后数组无法弹性伸缩。

       HashMap还有其他方法,有兴趣的可以慢慢看。     

0 0
原创粉丝点击