HashMap扰动函数、负载因子、扩容链表优化
从HashCode为什么选择31开始
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
从源码中可以得到,hash值是31与val的每一位累 +得到的。
循环公式如下:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
在StackOverflow中:
高赞回答:
翻译:
之所以选择值31,是因为它是一个奇数素数。如果是偶数,乘法溢出,信息就会丢失,因为乘以2等于移位。使用素数的优点不太清楚,但它是传统的。31的一个很好的特性是,乘法可以用移位和减法来代替,以获得更好的性能:31*i==(i<<5)-i。现代虚拟机会自动进行这种优化。
HashMap扰动函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
为什么需要扰动函数进行hash扰动
理论上来说字符串的hashCode
是一个int类型值,那可以直接作为数组下标了,且不会出现碰撞。但是这个hashCode
的取值范围是[-2147483648, 2147483647],有将近40亿的长度,谁也不能把数组初始化的这么大,内存也是放不下的。
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 默认长度
我们默认初始化的Map大小是16个长度 DEFAULT_INITIAL_CAPACITY = 1 << 4
,所以获取的Hash值并不能直接作为下标使用,需要与数组长度进行取模运算得到一个下标值,也就是我们上面做的散列列子。
那么,hashMap源码这里不只是直接获取哈希值,还进行了一次扰动计算,(h = key.hashCode()) ^ (h >>> 16)
。把哈希值右移16位,也就正好是自己长度的一半,之后与原哈希值做异或运算,这样就混合了原哈希值中的高位和低位,增大了随机性。计算方式如下图;
- 使用扰动函数就是为了增加随机性,让数据元素更加均衡的散列,减少碰撞