前言
上一篇文章中在介绍set
方法的流程时,如果找到的索引位置Entry
的key
是null
,表示该Entry
已经过期了,就会调用replaceStaleEntry
用新的Entry
替换掉旧的,这篇文章就来看看这个方法都做了哪些事。
替换过期Entry
// 后一个索引
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
// 前一个索引
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
// 替换过期Entry
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 清理的位置
int slotToExpunge = staleSlot;
// 往前循环找
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// 往后找
for (int i = nextIndex(, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) { // 找到了key
e.value = value; // 更新数据
tab[i] = tab[staleSlot]; // 和hash定位的索引位置进行交换
tab[staleSlot] = e;
if (slotToExpunge == staleSlot) // staleSlot前一个位置就是null,所以第一个for没有执行
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot) // 第一个for没执行,更新为后面下一个过期节点位置
slotToExpunge = i;
}
// 没找到key,创建新的Entry
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 有过期的Entry,执行清理操作
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
首先看一下prevIndex
和nextIndex
方法,其实就是索引减一和加一,只不过到数组边界后又会继续从另一头继续,有点像循环链表。
第一个for
循环从当前key
的索引位置staleSlot
前一个开始往前找,只要该位置有数据,就调用Entry.get()
获取存储的key
,如果key
为null
,就修改slotToExpunge
为该索引,它的作用就是尽可能找到更多过期(Entry
的key
为null
)的节点。
第二个for
循环是从当前key
的位置staleSlot
的下一个位置开始往后找,如果找到了key
,就更新value
,然后和key
的hash
定位的索引位置数据进行互换,这个互换操作可以通俗地理解为:我计算出来数据应该是放在i
的位置,但是最后放在了j
的位置,把这两个位置的数据交换一下我的数据就在i
的位置了,这样下次找的时候就能直接找到,不用再挨个往后遍历了。
slotToExpunge
的初始值就是staleSlot
,而第一个for
循环是从前一个位置开始的,所以如果第一个for
循环执行过了slotToExpunge
就不可能是staleSlot
了,所以if (slotToExpunge == staleSlot)
这个条件成立的情况就是前一个位置就是null
,所以第一个for
循环就没有执行。上面进行数据交换其实就是对staleSlot
和i两个位置交换的数据,所以slotToExpunge
要重新执行i的位置。
expungeStaleEntry(slotToExpunge)
方法的作用是从slotToExpunge
位置开始往后找,只要没遇到null
的节点就把所有过期的节点都清除掉(这就理解第一个for
循环为啥是往前找了,先往前找到更多的过期节点,然后清除时从该位置往后清),然后把有效的数据重新计算hash
,移动到新的位置。(源码分析见下篇文章)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len)
方法的作用是继续清理过期的数据。(源码分析见下篇文章)
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
slotToExpunge == staleSlot
和前面一样表示还是初始位置没有往前找,如果k == null
表示当前位置节点为过期节点,就更新slotToExpunge
为i
的位置,也就是往后移了一位。
如果第二个for
循环没有找到key
,就直接清除掉staleSlot
位置的数据,然后创建新的节点把数据存到该位置。然后如果lotToExpunge
为位置发生过变化,表示有过期的Entry
,就执行清理操作。