ConcurrentHashMap中链表拆分

目录

前言

一、场景

二、详细步骤

1.推演过程

2.示例代码输出

三、源码

总结


前言

基于JDK7,描述数组扩容时,链表元素如何移动至新数组中。


一、场景

数组发生扩容时,链表中所有元素需要移动至新数组相应位置。在HashMap中,是遍历链表,依次将元素移动至新数组相应位置。而在ConcurrentMap中,是将原有链表逻辑拆分成两个链表,移动将两个链表的头节点,直接放至新数组的相应下标位置。

二、详细步骤

1.推演过程

核心逻辑就是ConcurrentHashMap源码,现通过LinkTest链表示例代码模拟,如下:

public class LinkTest {
    private LinkTest next;
    private int value;
    private int hash;

    public LinkTest(int value,LinkTest next,int hash){
        this.value = value;
        this.next = next;
        this.hash = hash;//模拟key的hash值
    }
    public static void main(String[] args) {
        mockJDK7(generate());        
    }
    public static LinkTest generate(){
        LinkTest tail = new LinkTest(55,null,1);
        LinkTest l2 = new LinkTest(44,tail,1);
        LinkTest l3 = new LinkTest(33,l2,2);
        LinkTest l4 = new LinkTest(22,l3,2);
        LinkTest l5 = new LinkTest(11,l4,1);
        return l5;
    }    
   /**
     * 模拟jdk7中concurrentHashMap数组扩容后,链表元素移动逻辑
     * @param e 链表首节点
     */
    public static void mockJDK7(LinkTest e){
        LinkTest[] newTable = new LinkTest[2];//模拟扩容后的数组。
        LinkTest next = e.next;

        LinkTest lastRun = e;
        int lastIdx = e.hash & 1;//模拟链表拆分时的数组下标,只有2个位置可能。hash为1或2,所以用奇、偶数模拟
        //循环一:模拟循环链表
        for (LinkTest last = next;
             last != null;
             last = last.next) {
            int k = last.hash & 1;
            if (k != lastIdx) {
                lastIdx = k;
                lastRun = last;
            }
        }
        //链表循环结束
        newTable[lastIdx] = lastRun;
        //循环二
        for (LinkTest p = e; p != lastRun; p = p.next) {
            int v = p.value;
            int h = p.hash;
            int k = h & 1;
            LinkTest n = newTable[k];//头插法,通过向下移动链表实现
            newTable[k] = new LinkTest(v, n,h);
        }
        for(int i=0; i<newTable.length; i++){
            StringBuffer sb = new StringBuffer();
            LinkTest curr = newTable[i];
            while(curr!= null){
                sb.append(curr).append(",");
                curr = curr.next;
            }
            System.out.println("the original list[" + i + "]: " + sb);
        }
    }
    public static LinkTest generate(){
        //简化场景,hash值只模拟1、2
        LinkTest tail = new LinkTest(55,null,1);
        LinkTest l2 = new LinkTest(44,tail,1);
        LinkTest l3 = new LinkTest(33,l2,2);
        LinkTest l4 = new LinkTest(22,l3,2);
        LinkTest l5 = new LinkTest(11,l4,1);//返回头节点
        return l5;
    }    
}    

2.示例代码输出

原链表输出:
Node{value=11,hash=1, next=22},Node{value=22,hash=2, next=33},Node{value=33,hash=2, next=44},Node{value=44,hash=1, next=55},Node{value=55,hash=1, next=null}

第一个for循环结束,lastRun指向value=4,lastIdx=1,在赋值newTable[lastIdx]后:
newTable[1] = LinkTest{value=44,hash=1, next=55} -> Node{value=55,hash=1, next=null}
第二个for循环:
第一轮:newTable[1] = Node{value=11,hash=1, next=44} -> LinkTest{value=44,hash=1, next=55} -> Node{value=55,hash=1, next=null}
第二轮:newTable[0] = Node{value=22,hash=2, next=null}
第三轮:newTable[0] = Node{value=33,hash=2, next=22} -> Node{value=22,hash=2, next=null}

观察以上结果的推演过程可以发现:

  1. 上例中lastIdx在0与1之间来回切换,而lastRun将会指向链表最后一次0与1切换的元素。
  2. newTable保存了拆分后的2个链表的头节点;
  3. 通过此方式,就可以不用挨个移动链表所有节点,完成链表的转移;

三、源码

JDK7的源码是在private void rehash(HashEntry<K,V> node) 方法。


总结

通过上述示例代码,可以看出JDK7中的ConcurrentHashMap中如何可以不用挨个移动链表所有节点,就完成链表的转移;。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值