1. 简述-缘由
大家都挺了解 LruCache 主要做内存缓存的,比如 Glide, Picasso... 都是用的LruCache 算法, LRU 即 Least Recently Used,我们都知道 LruCache底层用的就是 LinkedHashMap ,它把 LRU 算法全部交给了 LinkedHashMap . 那我们现在就分析一下 LinkedHashMap .
2. 分析
2.1 先看继承关系
LinkedHashMap 继承 HashMap,所以 HashMap 有的特性, 它都有, 比如 线程不安全, 可以null 值, null value等等, 只不过 LinkedHashMap 底层用的是 数组 + 双向链表
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{}
2.2 看看节点
节点多了一个 前一个节点 和 后一个节点 (证明是双向链表)
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
LinkedHashMapEntry<K,V> before, after;
LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
2.3 看一看多出来的属性
// 双向链表的头部
transient LinkedHashMapEntry<K,V> head;
// 双向链表的尾部 Least Recently Used 也就是最少,最近用的
transient LinkedHashMapEntry<K,V> tail;
// 是否用 最少 最近的方式来 存储
// true则表示基于访问 ( put 或者 get ) 的顺序来排序,意思就是最近使用的节点,放在双向链表的尾部
// false则表示按照插入顺序来
final boolean accessOrder;
2.4 看一看构造方法
跟 HashMap 作比较 有多出来一个构造方法, 就是把 accessOrder 设置为true
public LinkedHashMap(int initialCapacity,float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
// 按照访问( put 或者 get )的顺序 存储 数据
this.accessOrder = accessOrder;
}
2.5 put() 方法(其实是重写了newNode构建节点方法 )
LinkedHashMap 并没有重写 put() 方法 , 依然调用父类的 put() 方法, 但是父类put()方法中, 有一个 newNode(), LinkedHashMap 只重写了 newNode() 来创建节点
//构建LinkedHashMap.Entry节点
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMapEntry<K,V> p = new LinkedHashMapEntry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
//将新增节点放到到链表尾部
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
//如果直接集合是空的,把头部也写成这个节点
if (last == null)
head = p;
else {
//将新增的节点放到链表的尾部,排好指针
p.before = last;
last.after = p;
}
}
2.6 Lru 算法的方法
涉及到两个个方法afterNodeAccess() afterNodeInsertion() 方法
// 是否删除最老的那一个节点
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMapEntry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
// 如果是 true 就会删除最老的一个节点, afterNodeInsertion() 方法调用
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
3 总结
-
LinkedHashMap相对于HashMap的源码比,是很简单的。因为大树底下好乘凉。它继承了HashMap,仅重写了几个方法,以改变它迭代遍历时的顺序。这也是其与HashMap相比最大的不同。 在每次插入数据,或者访问、修改数据时,会增加节点、或调整链表的节点顺序。以决定迭代时输出的顺序。
-
accessOrder ,默认是false,则迭代时输出的顺序是插入节点的顺序。若为true,则输出的顺序是按照访问节点的顺序。为true时,可以在这基础之上构建一个LruCache.
-
LinkedHashMap并没有重写任何put方法。但是其重写了构建新节点的newNode()方法.在每次构建新节点时,将新节点链接在内部双向链表的尾部
-
accessOrder=true的模式下,在afterNodeAccess()函数中,会将当前被访问到的节点e,移动至内部的双向链表的尾部。值得注意的是,afterNodeAccess()函数中,会修改modCount,因此当你正在accessOrder=true的模式下,迭代LinkedHashMap时,如果同时查询访问数据,也会导致fail-fast,因为迭代的顺序已经改变。
-
nextNode() 就是迭代器里的next()方法 。 该方法的实现可以看出,迭代LinkedHashMap,就是从内部维护的双链表的表头开始循环输出。 而双链表节点的顺序在LinkedHashMap的增、删、改、查时都会更新。以满足按照插入顺序输出,还是访问顺序输出。
-
它与HashMap比,还有一个小小的优化,重写了containsValue()方法,直接遍历内部链表去比对value值是否相等。