LinkedHashMap

Collection 系列文章的总目录:

HashMap 相比,LinkedHashMap 是插入有序的,即它能保证元素的插入顺序。

原理:

节点加入了 beforeafter 指针(引用),用来指向上一个和下一个节点:

新类型的节点:

// 使用成员变量保存:记录顺序的链表的头和尾
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;

// 可以看到增加了before和after指针
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

顺序:

  • LinkedHashMap 默认保存的是元素的插入顺序
  • 可以通过构造传入 accessOrder 来指定保存的顺序
    • false:默认,即元素的插入顺序,替换已有元素不会影响它的顺序
    • true:保存元素的访问顺序,即 getput替换已存在的元素都会更新元素的访问顺序

添加元素:

HashMap 的 putVal:模板方法模式

LinkedHashMap 继承了 HashMap,HashMap 中定义了模板方法 putVal,声明成 final

然后在里面调用了很多模板方法,这些模板方法 HashMap 自己实现了,子类也可以覆盖模板方法,从而提供不同的实现

在 LinkedHashMap 中就是通过覆盖这些方法来实现的,而不需要自己再写一个 putVal 方法

// 创建新类型的节点,并更新记录顺序的链表
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}

// 访问节点后,如果accessOrder为true,则更新记录顺序的链表
void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

// 可能会删除最旧的元素,取决于removeEldestEntry()方法的时候,默认返回false
void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

实现 LRU:

使用 LinkedHashMap 实现 LRU:

  • LRU:Least Recently Used,最近最少使用。即将旧的、很久不访问的数据删除

  • 由于 LinkedHashMap 会保存更新的顺序,所以可以用来实现 LRU

实现:

  • accessOrder 要设置为 true,因为访问元素之后,这个元素就刷新了。当然替换元素也会刷新

  • 覆盖 removeEldestEntry() 方法

摘抄自: A LRU Cache in 10 Lines of Java

import java.util.LinkedHashMap;
import java.util.Map;
public LRUCache<K, V> extends LinkedHashMap<K, V> {
    private int cacheSize;
    public LRUCache(int cacheSize) {
        super(16, 0.75, true);
        this.cacheSize = cacheSize;
    }
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() >= cacheSize;
    }
}

缺点:

  • 维护双链表或链表+红黑树的结构,性能有损失
  • 不是线程安全的

所以实践中还是使用更加成熟的 LRU 方案

posted @ 2020-03-30 00:53  demo杰  阅读(575)  评论(0编辑  收藏  举报