当我们put元素的时候, 首先会对key进行hash计算, 公式: (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
公式拆解:
key等于null吗?等于返回0, 不等于返回 (h = key.hashCode()) ^ (h >>> 16)
h = key.hashCode() 即 计算key的hashCode值, 赋值给h
h >>> 16 将h向右移16位
(h = key.hashCode()) ^ (h >>> 16) 将key的hashCode值和移位后的hashCode值进行异或运算
ps: hashCode()是一个本地方法, 我们看不到源码
ps:异或运算: 异或运算法则为相同取0,不同取1 例: 3^10=9 即转成二进制 0011^1010 = 1001
计算好hash值后, 会根据数组的长度和hash值进行与运算然后计算索引的位置
通过上面两行代码我们知道了hashMap的索引位置是如何计算的, 下面我们回到正题, 为什么HashMap的大小一定要是2的幂次方呢?我们看一下resize()扩容方法;
我们重点看一下do...while...这段代码, 从方法中可以看出,当hash值和原容器大小进行与运算等于0时,索引位置不变,当hash值和原容器大小进行与运算不等于0时, 索引位置=原索引位置+原容器大小. 这就是为什么hashMap的大小为什么一定要是2的幂次方. 如果容器大小不是2的幂次方, 扩容计算时就不会满足下面这段代码逻辑.因此map容器大小设置成2的幂次方可以简化扩容时的代码逻辑, 也大大提高了扩容时的性能.
下面一段代码是我写的一个demo, 摘取了map的hash计算和索引计算的公式, 可以试着修改一下mapSize大小, 来看下一当mapSize不是2的幂次方的时候, 上面的逻辑是不通的, 如果索引位置没变可以试着多改几次验证看看
public class Demo {
public static void main(String[] args) {
//初始容量为8 改成16时索引未变, 改成32的时候会出现, 改成31 102的位置是17 不是3+16
Integer mapSize = 30;
//因为初始大小只有2, 所以a,c经过hash计算会存到一个索引1,链表下, b,d会存到一个索引0,链表下
getIddex("102", mapSize);
getIddex("103", mapSize);
getIddex("114", mapSize);
getIddex("115", mapSize);
}
/**
* 计算索引位置
* @param key
* @param mapSize
*/
private static void getIddex(String key, Integer mapSize) {
int h;
//计算hash值
int hash = (h = key.hashCode()) ^ (h >>> 16);
System.out.println(key + "=====hash : " + hash);
//根据mapSize和hash值计算索引
int index = (mapSize - 1) & hash;
System.out.println(key + "=====index : " + index);
System.out.println("=========================================");
}
}
最后, 希望此文章对大家有所帮助, 其中有任何问题欢迎随时指出, 欢迎大家共同探讨学习.