快捷搜索: 王者荣耀 脱发

HashMap 扰动函数、负载因子、扩容链表拆分

1.扰动函数

在jdk8中,hashmap有这样一段代码,他叫扰动函数,目的是优化散列效果

static final int hash(Object key) {
          
   
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
我们获取到的key.hashCode()哈希值,哈希的取值范围是[-2147483648, 2147483647],那我们能拿这个数来当作下表吗?很显然不太行,那就需要数组的大小做与运算,hashmap的初始数组大小是1 << 4 ,也就是16,那计算数组的下标同过与运算的方式获得,(16-1)&h。

1. 那么为什么我要使用这个扰动函数呢?我们不用扰动函数行不行? 使用扰动函数的目的就是为了优化散列,从而减少hash碰撞。

2. 那我们为什么选择数组长度要选择2的幂次方? 假如数组长度是8,那获取下标索引 (8-1)& h,数组长度就会出现一个0111除了高位以为都是1的特征,也是为了散列。

2.初始容量

    hashmap初始容量大小是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    那我们都直到数组的长度是选择2的幂次方的,那假如我们传入的默认长度是17,那么就要找比初始值大的那个最小2的幂次方,2的5次幂32>17,那么传入17的默认容量大小就是32。

那么底层是有一段代码去支撑的:

static final int tableSizeFor(int cap) {
          
   
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

这个n|=n>>>1,2,4,8,16这个右移主要是把右边都填上1,假如我们输入的是17

3.负载因子

负载因子决定了什么时候进行扩容,为什么不等把所有数组中所有下标都用完之后在扩容呢?其实就是为了减少hash碰撞,从而提高效率。当负载因子设置0.75的时候,假设初始容量是16,那么当增加到13个数的时候,就会进行扩容操作。

4.扩容链表拆分

为什么扩容,因为数组长度不足了。那扩容最直接的问题,就是需要把元素拆分到新的数组中。拆分元素的过程中,原jdk1.7中会需要重新计算哈希值,但是到jdk1.8中已经进行优化,不再需要重新计算,提升了拆分的性能,设计的还是非常巧妙的。

那么jdk1.8是如何做优化的呢? 假如原数组是16,现在扩容到32,那么原数组hash与增长出来的长度16做与运算,的出来的结果如果为0则下标不表,如果新位置就是原来的位置加16。

经验分享 程序员 微信小程序 职场和发展