LinkedHashMap

368 阅读4分钟

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值是否相等。