jdk1.7中,hashmap是用数组+单链表实现的,jdk1.8中又加入了红黑树。
jdk1.7
构造方法:做一些初始化工作
put方法
Q1:2的幂次方数-1后转化为二进制(2-1:0000 0001 4-1:0000 0011 8-1:0000 0111 16-1:0000 1111 32-1:0001 1111)左全0右全1,无论你的hash值是什么,(hash)与(2的幂次方数-1)做&运算,取值范围都在[0, 2的幂次方数-1]。此外,动态扩容后计算位置元素能均匀分布,因为动态扩容也是按照2的幂次方数,比如从16扩容到32,重新计算后的下标要么不变,要么就会+16。
比如,A的二进制数是 1100 0010,B的二进制数是 1001 0010,它们与(16-1)做&运算后,都是0000 0010,换算成十进制是2;扩容后则与(32-1)做&运算,A得到 0000 0010,换成十进制是2,B得到 0001 0010,换成十进制是18。
Q2:当hashmap的容量比较小,而hash值比较大的时候,hash冲突就容易增多,从Q1知道,获取下标只取决于低位,因为高位无论怎么变,都会0&操作变成0,那么它的下标都是同一个,这就会导致某一链条特别长,get()的效率就会变低。在hash(Object k)方法中,通过 >>> 和 ^ 运算符,计算新的hash值。(>>> 和 ^)的运算过程:比如 A:1110 1010通过>>>运算符(高位补0,低位省略),假设右移四位,即得到B:0000 1110,然后A与B做 ^运算,这样的好处是A的高位也可以参与进来计算。
get方法
查询类似put方法,先根据hash值与table容量大小获取下标,然后遍历链表
jdk1.8
构造方法:做一些初始化工作
put方法
按原来例子解释下链表拆分: A的二进制数是 1100 0010,B的二进制数是 1001 0010,原容量是16(0001 0000),
A&16=0000 0000即十进制是0,B&16=0001 0000即十进制是16,那么根据 e.hash & oldCap = 0 来分组,可分为0一组,非0一组。