目录
前言
基于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}
观察以上结果的推演过程可以发现:
- 上例中lastIdx在0与1之间来回切换,而lastRun将会指向链表最后一次0与1切换的元素。
- newTable保存了拆分后的2个链表的头节点;
- 通过此方式,就可以不用挨个移动链表所有节点,完成链表的转移;
三、源码
JDK7的源码是在private void rehash(HashEntry<K,V> node) 方法。
总结
通过上述示例代码,可以看出JDK7中的ConcurrentHashMap中如何可以不用挨个移动链表所有节点,就完成链表的转移;。