面试的时候经常会遇到这样的问题,HashMap
的容量为什么是 2 的幂,key
的 hash 值是怎么结算的。
其实单看源码,你可能只知其然,不知其所以然。从 JDK 17 的源码中可以看到最终计算 key 的 hash 最终调用的方法代码如下
/**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
其实英文好点的人看注释也能看出一些名堂出来,这大致是讲将 key 的 hash 值的高半区与低半区做异或,因为计算 key 在 node 数组里的下标的时候会用到 2 的幂做掩码(取余),因为高位没参与取余运算,一些设计不太好的 hash 函数(例如散列集成等差数列分布的 hash 函数)经过取余后可能会发生大量冲突而影响性能,当然这只是少数场景。注释也说了大多数 hash 函数的散列集都是合理分布的,这个操作不一定会带来性能提升。。。这里只是以较低代价减少可能的冲突而已。
那么前面也说了,计算下标的时候用 2 的幂做取余。这是因为用 2 的幂来取余可以通过移位操作快速实现。
至于为什么要取余,很显然 hash 值的地址空间是 int 范围。。。 而我们实际不可能创建这么大的数组来储存数据。当然要通过取余操作把 key 的下标限定在指定容量范围内了。