一、分析hashmap之前需要先了解位运算
Java中的<< 和 >> 和 >>> 详细分析
<<表示左移移,不分正负数,低位补0;
注:以下数据类型默认为byte-8位
左移时不管正负,低位补0
正数:r = 20 << 2
20的二进制补码:0001 0100
向左移动两位后:0101 0000
结果:r = 80
负数:r = -20 << 2
-20 的二进制原码 :1001 0100
-20 的二进制反码 :1110 1011
-20 的二进制补码 :1110 1100
左移两位后的补码:1011 0000
反码:1010 1111
原码:1101 0000
结果:r = -80
>>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;
注:以下数据类型默认为byte-8位
正数:r = 20 >> 2
20的二进制补码:0001 0100
向右移动两位后:0000 0101
结果:r = 5
负数:r = -20 >> 2
-20 的二进制原码 :1001 0100
-20 的二进制反码 :1110 1011
-20 的二进制补码 :1110 1100
右移两位后的补码:1111 1011
反码:1111 1010
原码:1000 0101
结果:r = -5
>>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0
正数: r = 20 >>> 2
的结果与 r = 20 >> 2 相同;
负数: r = -20 >>> 2
注:以下数据类型默认为int 32位
-20:源码:10000000 00000000 00000000 00010100
反码:11111111 11111111 11111111 11101011
补码:11111111 11111111 11111111 11101100
右移:00111111 11111111 11111111 11111011
结果:r = 1073741819
了解完位运算后,我们需要对其hashmap内部做一个了解
二、hash函数
以下内容参考:
今日头条
浅显理解 hashcode 和 hash 算法_stateiso的博客-优快云博客_hashcode怎么计算
HashMap中hash(Object key)原理,为什么(hashcode >>> 16)。_杨涛的博客的博客-优快云博客
hash函数的目标是计算key在数组中的下标
判断一个哈希函数的标准:散列是否均匀、计算是否简单
hashmap哈希函数的步骤:
1.对key对象的hashcode进行扰动
2.通过取模求得数组下标
扰动是为了让hashcode的随机性更高
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
也就是高16和低16位进行异或
>>> 16的话 32位前16位是0
我们知道异或运算两个相应位不同才为1,相同为0
那么我们理解 (h = key.hashCode()) ^ (h >>> 16)
h >>> 16 高16为0 与 任意位异或得到的 还是那个位数
这个就会保持高16位不变
看下图:
以8位进行演示:
高四位还是保持原先hashcode值,高四位的值保持不变
高低位进行异或,让高位参与散列运算,散列更加均衡
扰动之后需要对结果取模
HashMap在jdk1.8并不是简单使用 % 进行取模,而是采用了另外一种更加高性能的方法
HashMap控制数组长度为2的整数次幂
hashcode进行求余运算 和 让hashcode与数组长度-1进行位与运算是 相同 的效果
位与运算的效率却比求余高得多,从而提升了性能
扩容运算中也利用到了该特性
取模运算的源码看到 putVal() 方法,该方法在 put() 方法中被调用
if ((p = tab[i = (n - 1) & hash]) == null)
完整的hash计算过程可以参考下图
HashMap的数组长度为2的整数次幂,那么HashMap是如何控制数组的长度为2的整数次幂的?
两种情况:
1.初始化时指定的长度
未在HashMap构造器中指定长度,则初始长度为16
当我们初始化指定一个非2的整数次幂长度时,HashMap会调用 tableSizeFor() 方法,转化成 大于该指定数的最小2的整数次幂
2.扩容时的长度增量
HashMap每次扩容的大小都是原来的两倍,控制了数组大小一定是2的整数次幂
小结:
HashMap通过高16位与低16位进行异或运算来让高位参与散列,提高散列效果;
HashMap控制数组的长度为2的整数次幂来简化取模运算,提高性能;
HashMap通过控制初始化的数组长度为2的整数次幂、扩容为原来的2倍来控制数组长度一定为2的整数次幂
三、hash碰撞
hash冲突指的是两个不同的key经过hash计算之后得到的数组下标是相同的
解决hash冲突的方式很多,如开放定址法、再哈希法、公共溢出表法、链地址法
HashMap采用的是 链地址法 ,jdk1.8之后还增加了红黑树的优化