JDK源码解析之LinkedHashMap

前言:

    在上一篇博客中,我们系统的介绍了HashMap,HashMap是非线程安全的类,一般情况下作为局部变量使用是完全可以的。

    HashMap的存放是无序的,按照一定的规则映射的,所以,遍历其元素的顺序与添加的顺序是不一致的。

    如果我们想按照添加的顺序来遍历元素该如何做呢?

    那就需要使用本次介绍的LinkedHashMap来完成了。


    注意:由于LinkedHashMap大量使用了HashMap的方法,所以不太明白HashMap工作原理的同学,可以先看下笔者另一篇关于HashMap的文章,https://blog.csdn.net/qq_26323323/article/details/86219905 

 

1.LinkedHashMap结构分析

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>{
    
    // 双端链表的头元素
    transient LinkedHashMap.Entry<K,V> head;

    // 双端链表的尾元素
    transient LinkedHashMap.Entry<K,V> tail;
    
    // 默认为false,指的是添加顺序;
    // 当指定为true时,访问顺序,这个在后面再详细说明
    final boolean accessOrder;
    
    // 不同于HashMap.Node
    // 继承了HashMap.Node,同时还扩展了自己的属性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就是HashMap与LinkedList的结合体。

    其继承HashMap,通过HashMap来实现基本功能;

    其添加head和tail属性,并且在Entry节点中,添加before和after属性,来实现双向链表的功能。

2.LinkedHashMap构造方法

// 1.空参构造
public LinkedHashMap() {
    // 直接调用new HashMap()
    super();
    accessOrder = false;
}

// 2.初始化数组容量
public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

// 3.初始化数组容量和负载因子
public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

// 4.初始化数组容量和负载因子和AccessOrder
public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

// 5.以一个Map来初始化
public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}

    之前介绍过HashMap,所以对上述super方法不再介绍,LinkedHashMap都是直接调用HashMap的方法

    注意这里的AccessOrder,默认值都是false。

3.添加操作

    发现在LinkedHashMap中没有找到put方法,说明其直接使用了HashMap.put()方法

    但是在LinkedHashMap中其重写了newNode()方法,有关于put方法的具体过程,笔者不再分析,有兴趣的可以看一下笔者上一篇关于HashMap的文章

// LinkedHashMap.newNode()
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;
}

// LinkedHashMap.linkNodeLast()
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    LinkedHashMap.Entry<K,V> last = tail;
    tail = p;
    // last为null,说明当前元素为第一个元素,直接社会为头元素
    if (last == null)
        head = p;
    else {
        // 将p元素,放置到链表的末尾
        p.before = last;
        last.after = p;
    }
}

    总结:添加操作一方面将节点存放到哈希表中,另一方面使用双端链表来维持其节点添加的前后关系

4.删除操作

    删除操作也是直接使用了HashMap.remove()方法,

    我们在关注HashMap.remove()方法时看到以下几个方法

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }// 在HashMap.removeNode()方法最后实现了这个空参方法

    这个afterNodeRemoval方法主要就是给LinkedHashMap使用的

// LinkedHashMap.afterNodeRemoval()
void afterNodeRemoval(Node<K,V> e) { // unlink
    LinkedHashMap.Entry<K,V> p =
        (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    p.before = p.after = null;
    if (b == null)
        head = a;
    else
        b.after = a;
    if (a == null)
        tail = b;
    else
        a.before = b;
}

    主要功能就是将双端链表中的元素删除掉

5.查询操作

    这个get方法,在LinkedHashMap中实现了

public V get(Object key) {
    Node<K,V> e;
    // 还是直接调用HashMap.getNode()方法
    if ((e = getNode(hash(key), key)) == null)
        return null;
    // 默认accessOrder为false,
    // 我们来看下,假如其为true时,afterNodeAccess在做什么
    if (accessOrder)
        afterNodeAccess(e);
    return e.value;
}

// LinkedHashMap.afterNodeAccess()
// 将被访问的node放到链表的最后
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;
    }
}

    这个查询操作的afterNodeAccess方法弄的一脸蒙圈,这个在干嘛呢?

    这个需要结合刚才提到的添加操作来一起看。

    HashMap.put()方法中有一个空实现方法afterNodeInsertion(),而LinkedHashMap默认实现了这个方法,如下所示

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    // 删除最早的元素,这时需要removeEldestEntry()==true
    // 但是这个方法在LinkedHashMap中直接返回了false
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

// LinkedHashMap.removeEldestEntry()
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

    还是没明白!!!

    接着看...

6.LinkedHashMap实现LRU算法

    很多缓存组件都有具体的缓存删除算法,LRU作为一种经典算法,最近最少使用算法,将最近最少使用的数据删除。

    摘自网友的一个实现,来自:https://www.cnblogs.com/xiaoxi/p/6170590.html  

public class LRUCache extends LinkedHashMap
{
    public LRUCache(int maxSize)
    {
        // 注意这个true,设置的就是LinkedHashMap.accessOrder
        super(maxSize, 0.75F, true);
        maxElements = maxSize;
    }

    // 这个方法似曾相识,就是刚才put()方法中调用的方法
    protected boolean removeEldestEntry(java.util.Map.Entry eldest)
    {
        // 如果map中添加的元素超过maxElement最大限制之后,这个就会返回true
        // 结合刚才put()方法,在添加新元素的时候就会删除最近最少使用的元素
        return size() > maxElements;
    }

    private static final long serialVersionUID = 1L;
    protected int maxElements;
}

    那么关键问题来了,什么叫做最近最少使用呢?

    我们使用LinkedList来维持元素的添加顺序,最新添加的元素放在链表尾部。

    如果元素在被访问后,就将元素移动到链表尾部,那么越靠近链表尾部的元素就越是活跃的元素,同理,在链表头部的元素则是已经很久没有访问的元素,符合我们的最近最少使用原则,则可以删除之。

    LinkedHashMap是如何实现的呢?我们在看一下其get()方法,其中调用了afterNodeAccess()方法,而这个方法就是将被访问的元素放置到链表尾。

    同时在做put操作的时候,我们重写了removeEledestEntry()方法,这样每次添加元素的时候都会检查是否超过最大元素个数,超过了则删除链表最头部的元素

参考:https://www.cnblogs.com/xiaoxi/p/6170590.html  

发布了124 篇原创文章 · 获赞 126 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/86243176