Java-HashMap中的扰动函数、负载因子与扩容链表拆分

发布时间 2023-06-27 00:20:00作者: 羊37

1.扰动函数

在hashmap中,put操作是这样进行的:

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

其中会涉及到hash(key)的运算,hash并不是直接使用hashCode(),而是这样:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这里的操作就称之为扰动函数

根据取模操作中的&和(length-1)这篇文章中提到的,计算出hash后我们可以使用&进行取模操作来确定放置在哪里。

现在问题是:计算hash时,为什么不直接用hashCode(),而是

// 把哈希值右移16位,之后与原哈希值做异或运算。
(h = key.hashCode()) ^ (h >>> 16)

先说结论:

增大随机性,优化散列效果,让数据元素更加均匀的散列(减少碰撞)。

1.1 分析

1.1.1 Hash的取值范围

在Java中,hashCode() 方法返回的哈希值是一个32位的有符号整数(int类型)。

因此,哈希值的取值范围是从 [-2147483648, 2147483647](即 -2^312^31 - 1)。

1.1.2 扰动函数的计算过程

对于扰动hash的函数的计算,做一个拆分。

    /**
     * 扰动函数计算hash
     *
     * @param key .
     * @return hash
     */
    final int hashAndLog(Object key) {
        int hashCode;
        if (key == null) {
            return 0;
        }

        hashCode = key.hashCode();
        int rightShift = hashCode >>> 16;
        int result = hashCode ^ rightShift;

        log.info("int: {},[sourceHash] hashCode             : {}", String.format("%-12s", hashCode), to2$Padding(hashCode, 32));
        log.info("int: {},[rightShift] hashCode >>> 16      : {}", String.format("%-12s", rightShift), to2$Padding(rightShift, 32));
        log.info("int: {},[result]     hashCode ^ rightShift: {}", String.format("%-12s", result), to2$Padding(result, 32));
        return result;
    }


    /**
     * int值转为2进制显示
     *
     * @param number  num
     * @param padding 填充0 总长度
     * @return Binary
     */
    public static String to2$Padding(int number, int padding) {
        String binaryString = Integer.toBinaryString(number);
        return String.format("%" + padding + "s", binaryString).replace(' ', '0');
    }

测试用例

   @Test
    void name4() {
        String str = "abc";
        int i = hashAndLog(str);
    }

输出结果

int: 96354       ,[sourceHash] hashCode             : 00000000000000010111100001100010
int: 1           ,[rightShift] hashCode >>> 16      : 00000000000000000000000000000001
int: 96355       ,[result]     hashCode ^ rightShift: 00000000000000010111100001100011

上面的过程可以用下面的图来表示

image-20230627001113593

2.负载因子与初始化容量

3.扩容元素拆分