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
。
就以数组长度分别为8
和7
为例,看下边的情况:
数字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
这方面的知识。