【JDK专题】——JDK数据结构——LinkedHashMap源码剖析

LinkedHashMap——基本结构

很明显,LinkedHashMap直接继承了HashMap;

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;
    /**
      * 很明显继承了原有Entry的属性,如果要实现按照插入和遍历顺序一样的需求
      * 不改变原有的属性,添加两个before、after
      * LinkedHashMap的node重名为Entry
      */
	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);
        }
    }
     /**
      *  重写了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;
    }
    ```java
    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
        /**
         * @hashMap只有一个节点(last == null)
         * 直接操作头等于新节点
         */
        LinkedHashMap.Entry<K,V> last = tail;
        tail = p;
        if (last == null)
            head = p;
        /**
         * @否则将末尾节点和新节点调整链表结构
         * 操作新节点的前一个节点是末尾节点(p是原末尾节点)
         * 操作旧末尾节点的后一个节点等新节点
         * 其实就是新节点放最后,形成双向链表
         */
        else {
            p.before = last;//操作新节点的前一个节点是末尾节点
            last.after = p;//操作旧末尾节点的后一个节点等新节点
        }
    }

HashMap原有的方法

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        ……
        /*
          针对于key是null的重写了newNode
         */
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
            ……
                /*
                  在链表中找到了key相同的值进行替换时,激活访问模式的回调
                 */
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
         /**
          激活LRU模式的回调
         */
        afterNodeInsertion(evict);
        return null;
    }
    /**
      * 这是一个生命周期方法,为了实现扩展
      * 原有的是空实现,LinkedHashMap重写了这些,我们下面逐个讲解这些方法
      */
    void afterNodeAccess(Node<K,V> p) { }//访问模式专用的链表调整
    void afterNodeInsertion(boolean evict) { }//LRU模式专用的链表调整
    void afterNodeRemoval(Node<K,V> p) { }//删除时的的链表调整
    

LinkedHashMap——遍历

public final void forEach(Consumer<? super K> action) {
            if (action == null)
                throw new NullPointerException();
            int mc = modCount;
            /*
            	遍历起始条件是:e首先=head;
            	遍历终止条件是:e==null;
            	遍历自增条件是:e.after,选择下一个;
             */
            for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
                action.accept(e.key);
            if (modCount != mc)
                throw new ConcurrentModificationException();
}

通过上面的案例,我们明显的看出在node数据结构中的before和after就是实现有序遍历的关键;那么具体在插入节点的时候是如何实现的呢?

LinkedHashMap——删除原理

删除肯定要将断层相连,所以怎么实现的呢?

 void afterNodeRemoval(Node<K,V> e) {
        LinkedHashMap.Entry<K,V> p =
                (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;

        /**
         * 原节点头尾变成空
         */
        p.before = p.after = null;

        /**
         * @原节点是头节点(前继节点是null)
         * 头节点直接等于后继
         * 否则前继的后继向后相连
         */
        if (b == null)
            head = a;
        else
            b.after = a;
        /**
         * @原节点是尾节点(后继节点是null)
         * 否则前继的后继相连向前相连
         */
        if (a == null)
            tail = b;
        else
            a.before = b;
    }

LinkedHashMap——访问模式原理

访问模式下,访问元素和插入都会影响链表的双向链表顺序的,这是怎么设计出来的呢?(非访问模式,就是按照插入的时候的linkNodeLast决定顺序即可)
在这里插入图片描述

     final boolean accessOrder;
     public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        /*
          当元素被访问到后就会进行一次重新的before和after编排
         */
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
            ……
               /*
                    在链表中找到了key相同的值进行替换时
                    进行一次重新的before和after编排
                */
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        return null;
    }
    /**
     * @滞末处理
     * 更改节点至尾部,显然要处理之前节点把断层补上
     */
    void (Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        /**
         * @条件开启了accessOrder访问顺序模式且如果旧节点不是末尾节点才进入滞末处理
         * last指向末尾节点
         */
        if (accessOrder && (last = tail) != e) {
            
            
            /**
             * e代表旧的刚被替换的节点,p是强转后的e,兼容hashMap的方法原型
             * b代表被替换的前继,a代表被替换的后继
             */
            LinkedHashMap.Entry<K,V> p =
                    (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
            p.after = null;//即将被调整后最后,所以最后肯定是null但是指针已经在前面保留了
            /**
             * @去除旧节点连接两个节点中间的断层
             */
            /**
             * @(1)旧节点是头节点(没前继,前继是空)
             * 直接头结点指向自己的后继
             */
            if (b == null)
                head = a;
            else //否则旧节点的前继节点向后连接旧节点的后继节点
                b.after = a;
            /**
             * @(2)旧节点是中间节点(后有继不是空)
             * 直接旧节点的后继向前连接旧节点的前继,刚好上面相反
             */
            if (a != null)
                a.before = b;
            else //否则曾经是最后一个节点,将last指向前继,继续处理
                last = b;
            /**
             * @(3)处理只有一个节点时,last也是空(last指向前继是空或者tail是空)
             */
            if (last == null)
                head = p;
            else {

                /**
                 * 这里算是一个兜底策略,可能有两个情况
                 * 自己是中间节点,last就是tali,做一下指针调整
                 * 自己是最后一个节点,last是节点的前继节点,就是上面那个情况,曾经是最后一个节点,所以前继就是倒数二个,做一下这两个的指针调整
                 */
                p.before = last;
                last.after = p;
            }
            tail = p;//调整整体指针
            ++modCount;
        }
    }
     void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        /**
         * @此次实现LRU算法
         * 需要子类继承LinkHashMap实现removeEldestEntry方法,该方法传入头结点以及某种逻辑决定
         * 是否移除头节点,因为头节点意味着经常不被访问
         */
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

LinkedHashMap——LRU算法实现

在插入的时候有这么一个回调处理,

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        ……
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

LinkedHashMap的LRU方案
需要继承LinkedHashMap并实现removeEldestEntry方法的逻辑
也就是LinkedHashMap默认不支持LRU,removeEldestEntry默认返回false

  void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        /**
         * @此次实现LRU算法
         * 需要子类继承LinkHashMap实现removeEldestEntry方法,该方法传入头结点以及某种逻辑决定
         * 是否移除头节点,因为头节点意味着经常不被访问
         */
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

LinkedHashMap——总结

双向链表:node被包成entry多了一个before和after;然后在整体多了两个头指针和尾指针;
普通模式:只是在插入的时候更新尾部节点和新节点的指针关系
访问模式:除此以为get方法也会影响节点的顺序调整,这一切都通过afterNodeAccess回调实现;afterNodeAccess方法还有在插入的时候替换旧元素的时候也会激活;
LRU模式:默认一直不实现,就是删除头节点;需要继承LinkedHashMap重写LRU算法的实现决定true和false来实现整个

GodSchool 致力于简洁的知识工程,输出高质量的知识产出,我们一起努力 博主私人微信:supperlzf
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值