前言
上一篇文章中在介绍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,就执行清理操作。