在分析LinkedHashMap源码之前, 我们先看下LinkedHashMap在MyBatis缓存中的应用。我们知道LinkedHashMap继承于HashMap,所以底层结构还是数组+链表/红黑树。它的特殊之处在于维护了一个双向链表,使得在遍历Map时,输出是有序的。至于排序规则,是根据accessOrder变量来决定是按照插入顺序,还是按照访问顺序进行排序。
// 先进先出
// 最近最少使用缓存
public class LruCache implements Cache {
// Cache和Map作用分别是什么?
// 添加和删除的时候对这两个都进行操作了
private final Cache delegate;
private Map
LruCache 最近最少使用缓存,核心就是覆盖 LinkedHashMap.removeEldestEntry方法,返回true或false表示 LinkedHashMap要不要删除此最老键值。返回true,会对eldestKey进行赋值,赋值后在putObject方法中调用的cycleKeyList(key)方法可知,会移除最老元素。
在分析了MyBatis的LruCache缓存的设计后,我们对于LinkedHashMap的应用有了一定的认识,下面我们来分析LinkedHashMap的源码。对于LinkedHashMap的get、put方法,这里不再赘述。本文重点描述LinkedHashMap如何维护双向链表,即双向链表的建立过程、节点删除过程以及对于LinkedHashMap中元素的有序访问是如何实现的。
LinkedHashMap的存储结构是数组+链表/红黑树,如下图所示
LinkedHashMap的Entry节点继承了HashMap的Node节点。Entry除了包括HashMap的Node节点信息外,还有两个前后指针。
Node节点属性:
final int hash;
final K key;
V value;
Nodenext;
static class Entry extends HashMap.Node {
Entry before, after;
Entry(int hash, K key, V value, Node next) {
super(hash, key, value, next);
}
}
从下面的图可以看出TreeNode、Entry、Node和Entry之间的关系
为什么要这么设计?
原因后续补上
LinkedHashMap没有重写HashMap的put、get方法。当有元素插入时,开始建立双向链表。最初是head,tail指向头节点,后面再插入新元素时,移动tail节点。节点与节点之间的关系通过before和after两个变量来维护。
我们先看下HashMap的put方法
public V put(K key, V value) {
// onlyIfAbsent 传入的是false
// 如果是true,表示如果存在相同的就不覆盖已经存在的value值
// 为false 表示覆盖相同key的value值
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// key已经存在
if (e != null) { // existing mapping for key
V oldValue = e.value;
// value为空,更改value值
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
上面的putVal()方法中,调用到的newNode(hash, key, value, null)方法,在LinkedHashMap中重写了。下面是在LinkedHashMap中newNode的方法实现
Node newNode(int hash, K key, V value, Node e) {
LinkedHashMap.Entry p =
new LinkedHashMap.Entry(hash, key, value, e);
linkNodeLast(p);
return p;
}
private void linkNodeLast(LinkedHashMap.Entry p) {
LinkedHashMap.Entry last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
参考:http://javax.xyz/2018/01/24/LinkedHashMap-源码详细分析(JDK1-8)/