hashmap链表循环问题

jdk 1.7 hashmap

1.7中是数据是先扩容后插入

链表循环问题发生在链表转移的方法中

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

如果元素个数已经达到数组阈值,则扩容,并把原来的元素移动过去。

假设HashMap初始化大小为4,插入个3节点,不巧的是,这3个节点都hash到同一个位置,如果按照默认的负载因子的话,插入第3个节点就会扩容,为了验证效果,假设负载因子是1
在这里插入图片描述
插入第4个节点时,发生rehash,假设现在有两个线程同时进行,线程1和线程2,两个线程都会新建新的数组。
在这里插入图片描述
假设 线程2 在执行到Entry<K,V> next = e.next;之后,cpu时间片用完了,这时变量e指向节点a,变量next指向节点b。

线程1继续执行,很不巧,a、b、c节点rehash之后在同一个位置7,开始移动节点

第一步,移动节点a
在这里插入图片描述
第二步,移动节点b
在这里插入图片描述
注意,这里的顺序是反过来的,继续移动节点c
在这里插入图片描述
这个时候 线程1 的时间片用完,内部的table还没有设置成新的newTable, 线程2 开始执行,这时内部的引用关系如下:
在这里插入图片描述
这时,在 线程2 中,变量e指向节点a,变量next指向节点b,开始执行循环体的剩余逻辑

Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;

执行之后的引用关系如下图
在这里插入图片描述
执行后,变量e指向节点b,因为e不是null,则继续执行循环体,执行后的引用关系
在这里插入图片描述
变量e又重新指回节点a,只能继续执行循环体,这里仔细分析下:
1、执行完Entry<K,V> next = e.next;,目前节点a没有next,所以变量next指向null;
2、e.next = newTable[i]; 其中 newTable[i] 指向节点b,那就是把a的next指向了节点b,这样a和b就相互引用了,形成了一个环;
3、newTable[i] = e 把节点a放到了数组i位置;
4、e = next; 把变量e赋值为null,因为第一步中变量next就是指向null;

所以最终的引用关系是这样的:
在这里插入图片描述
节点a和b互相引用,形成了一个环,当在数组该位置get寻找对应的key时,就发生了死循环。

另外,如果线程2把newTable设置成到内部的table,节点c的数据就丢了,看来还有数据遗失的问题。

jdk 1.8 hashmap

1.8中数据是先插入再扩容

//规避了8版本以下的成环问题
                    else { // preserve order
                        // loHead 表示老值,老值的意思是扩容后,该链表中计算出索引位置不变的元素
                        // hiHead 表示新值,新值的意思是扩容后,计算出索引位置发生变化的元素
                        // 举个例子,数组大小是 8 ,在数组索引位置是 1 的地方挂着一个链表,链表有两个值,两个值的 hashcode 分别是是9和33。
                        // 当数组发生扩容时,新数组的大小是 16,此时 hashcode 是 33 的值计算出来的数组索引位置仍然是 1,我们称为老值
                        // hashcode 是 9 的值计算出来的数组索引位置是 9,就发生了变化,我们称为新值。
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        // java 7 是在 while 循环里面,单个计算好数组索引位置后,单个的插入数组中,在多线程情况下,会有成环问题
                        // java 8 是等链表整个 while 循环结束后,才给数组赋值,所以多线程情况下,也不会成环,也就是找到链表最后一个值之后才赋值,尾插法
                        do {
                            next = e.next;
                            // (e.hash & oldCap) == 0 表示老值链表
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            // (e.hash & oldCap) == 0 表示新值链表
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        // 老值链表赋值给原来的数组索引位置
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        // 新值链表赋值到新的数组索引位置
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }

整理自:https://www.jianshu.com/p/1e9cf0ac07f4

https://www.cnblogs.com/wen-he/p/11496050.html

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值