Map 中 put 和 get 的优化
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
举个例子
key="ykx"
key的hashCode是=119718 这是个10进制数
原始hashCode对应二进制11101001110100110==00000000000000011101001110100110
然后我再向右位移16位后的二进制==00000000000000000000000000000001
异或运算
0000000000000001 1101001110100110
0000000000000000 0000000000000001
0000000000000001 1101001110100111==119719 这个就是最终的hash值
优化点在哪
HashMap在get方法去获取值时是这样的
hash(key)&(n-1) 进行与运算而找到数组位置
0000000000000001 1101001110100111 上面优化后的hash值 hashMap默认数组长度16
0000000000000000 0000000000001111 &
0000000000000000 0000000000000111 =7
这就是最终的位置下标 只要数组长度是2的n次方 你会发现:119719%16与119719&(16-1)的值时相同的
因为与运算的性能会比取模效率高。
总结
其实这里最重要的几个点就是在算hash值时高是16位与低16进行了异或运算(因为很有可能有两个不同的值hash高16位不相同低16位相同)。这样就导致hashmap寻址运算时低16位已经包含了高16位与低16 的特征,因为在寻址的时候大多数都是低16位在运算,因为数组长度-1的数字大小一般情况都比较小。所以在get寻址时基本都是低16在运算,尽量避免hash冲突,寻址时用与运算代替取模运算也是比较大的优化,只要当hashmap的数组长度是2的n次方那么我们算出来的hash值取模这个长度等于与运算这个长度-1 的值。。
如果说冲突了会怎样
当hash冲突时会在当前下标建立一个链表来维护。jdk为了维护在遍历时性能够好,当链表长度大于8的时候回转化为红黑树,这时查询的时间复杂度就是O(logn) 。
为什么是8而不是4:我的理解是log2(8)=3而log2(4)=2 由于是红黑树、B+tree等 一般都是3层性能就是折中方案。
HashMap扩容后怎么重新定义位置
默认是16长度,以后每次扩容时 都会重新遍历原来的值拿到hash与 新的数组长度-1 进行与运算放到新的位置。