[HashMap源码学习之路]---hashcode的作用及数组长度为什么是2的n次幂

HashMap中的hashcode作用

  HashMap是Java 中很重要的一个概念,工作中使用的频率也非常广泛,需要对其进行了解。
  看源码是很枯燥的,但是看懂了,却有种豁然开朗的感觉,觉得特别棒,本篇只说hashcode的作用及数组长度为什么是2的n次幂
  首先点开HashMap的类,我是用开发工具idea ,新建了一个HashMap,点进去找到put方法。以jdk 1.8为例,如下:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

  然后再点进putVal 方法,则会看到有下面的代码:

tab[i = (n - 1) & hash]

1、HashMap 中为什么需要一个hashCode 值?

  原因就是需要用它来对HashMap 数组的位置来定位,如果向HashMap 里存一个数,单纯的依次使用equals 方法比较key 是否相同来确定当前数据是否已存储过,那效率非常低,而通过比较hashCode 值,效率就会大大提高,那它是如何定位数组位置呢,如果你使用的是jdk 1.8,那在put方法中的putVal方法里会看到如下内容:

  对于上边的n-1 后边会说到,先说一下上边的写法,& 为二进制中的与运算 ,它的运算特点是,两个数进行& ,如果相同则为1,不同则为0。
  因为hashMap 的数组长度都是2的n次幂 ,那么对于这个数再减去1,转换成二进制的话,就肯定是最高位为0,其他位全是1 的数,那以数组长度为8为例(默认HashMap初始数组长度是16),那8-1 转成二进制的话,就是0111
  那我们举一个随便的hashCode值,与0111 进行与运算 看看结果如何,如下:

 第一个key:      hashcode值:10101000    
   与0111进行&运算        &       0111                                      
                                 0000  (十进制为0)
   ------------------------------------------               
 第二个key:      hashcode值:11101000    
   与0111进行&运算        &       0111      
                                 0000  (十进制为0)
 --------------------------------------------               
 第三个key:      hashcode值:11101010    
   与0111进行&运算        &       0111      
                                 0010  (十进制为2)

  你可以随便变hashcode 值来测试,最终得到的数都会小于8 ,当然会像上边一样,出现相同的数据,那样的话,就会以链表的形式存在那个数组元素上了。
  回过头来再设想一下,那我们就可以通过把key转为hashCode值,然后与数组长度进行与运算 ,来确定HahsMap 中当前key对应的数据在数组中的位置
  原本,需要查找数组下的每个元素,以及他们对应的链,疯狂的调用equals 方法,誓死遍历出数据的方式,变成了仅仅是查找数组下的一个元素,然后,只需要equals 比较这一条链上的数据就可以了,这样equals 的使用次数降低了很多。
  通过上边的说明,不知道大家是否理解到了hashCode 值,在HashMap 中的作用呢?

2、为什么hashMap 的数组长度为2的n次幂

  还是上边那行代码:

tab[i = (n - 1) & hash]

  我们知道了& 的作用,但是n-1 到底是什么意思呢?
其实上边,已经提到过了,就是2的n次幂 是很特殊的数,随便满足这个条件的数,对它减去1,转换成二进制,都会有这样的特点,即,最高位为0,其他低位全为1
  就以数组长度分别为87 为例,看下边的情况:

数字8减去1转换成二进制是0111,即下边的情况
 第一个key:      hashcode值:10101001    
   与0111进行&运算        &       0111                                      
                                 0001  (十进制为1)
   ------------------------------------------                           
 第二个key:      hashcode值:11101000    
   与0111进行&运算        &       0111      
                                 0000  (十进制为0)
--------------------------------------------               
 第三个key:      hashcode值:11101110    
   与0111进行&运算        &       0111      
                                 0110  (十进制为6)

  这样得到的数,就会完整的得到原hashcode 值的低位值,不会受到与运算对数据的变化影响。

数字7减去1转换成二进制是0110,即下边的情况
 第一个key:      hashcode值:10101001    
   与0111进行&运算        &       0110                                      
                                 0000  (十进制为0)
   ------------------------------------------                           
 第二个key:      hashcode值:11101000    
   与0111进行&运算        &       0110      
                                 0000  (十进制为0)
--------------------------------------------               
 第三个key:      hashcode值:11101110    
   与0111进行&运算        &       0111      
                                 0110  (十进制为6)

  通过上边可以看到,当数组长度不为2的n次幂 的时候,hashCode 值与数组长度减一做与运算 的时候,会出现重复的数据,因为不为2的n次幂 的话,对应的二进制数肯定有一位为0 ,这样,不管你的hashCode 值对应的该位,是0 还是1 ,最终得到的该位上的数肯定是0 ,这带来的问题就是HashMap 上的数组元素分布不均匀,而数组上的某些位置,永远也用不到。如下图所示:
这里写图片描述
  这将带来的问题就是你的HashMap 数组的利用率太低,并且链表可能因为上边的(n - 1) & hash 运算结果碰撞率过高,导致链表太深。(当然jdk 1.8已经在链表数据超过8个以后转换成了红黑树的操作,但那样也很容易造成它们之间的转换时机的提前到来)。
  以上即是我理解的HashMap这方面的知识。

猜你喜欢

转载自blog.csdn.net/wohaqiyi/article/details/81161735