HashMap.put源码分析

HashMap.put 处理流程

HashMap.put

容量2的倍数?元素位置怎么确认的?

最简单的散列函数就是,按容量取模。位运算性能高于取模,但是如果容量不是2的倍数,就会导致数组中有些位置没有机会放置元素;

例如容量为3,二进制位就是 10 ,由于第一位为0,位与的结果第一位一直是0,三个位置(00,01,10)中,01 位置没机会放置元素,浪费空间;

容量值0位越多,浪费的空间越多。如果容量值为2的倍数,则容量值减1,二进位全是1,所有位置都有机会放置元素。

hash值计算,低位和高位异或,在容量小的情况下,更加散列,减少冲突:

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

位置:(capacity - 1) & hash;

扩容前,

oldCap-1就是0到n-1二进制位都是1;

扩容后,

newCap-1 就是0到n二进制位都是1;

如果hash 的第n位二进制位是0,计算的位置就和扩容前的一样,若为1则位置比扩容前大了一个oldCap,即 新位置=旧位置+oldCap;

所以扩容时,直接用hash & oldCap 来判断,等于0位置不动,等于1则,新位置=旧位置+oldCap,相比于取模性能高。

为什么负载因子是0.75?

负载因子越大,空间利用率越高,冲突机率越大,增加结构复杂性,查找成本高;

负载因子越小,空间利用率越小,冲突率低,查找效率高;

冲突率和空间利用率,0.75是个最佳实践

为什么树化?树化和反树化时机?

红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。加快检索速率。

插入元素后,判断链表长度大于8,就树化;

扩容时,位置上是红黑树结构,有一部分放置旧位置形成一个链表,另一部分放置到新位置形成一个链表,对于两个链表长度大于6,转换成红黑树,否则就以链表结构;

线程不安全的原因

1.在jdk1.7中,在多线程环境下,头插法,扩容时会造成环形链或数据丢失。

2.在jdk1.8中,在多线程环境下,会发生数据覆盖的情况。

你可能感兴趣的:(HashMap.put源码分析)