从底层源码认知LinkedHashMap

简介

为什么我们需要LinkedHashMap:
相比于HashMap:遍历HashMap的顺序并不是HashMap放置的顺序,因为放置值是根据hash值确定,也就是无序。而LinkedHashMap,它虽然增加了时间和空间上的开销,构造了一个双向链表,但是LinkedHashMap保证了元素迭代的顺序。
该迭代顺序有两种,可以是插入顺序或者是访问顺序。LinkedHashMap继承了HashMap类,有着HashMap的所有功能(比如LinkedHashMap没有重写put,remove等方法,想加入元素直接调用父类的put,remove方法),还提供了记住元素添加顺序的方法。

所以简单来说:
1.LinkedHashMap 是在 HashMap 的基础之上,构建了一个双向链表。用于记录插入顺序或访问顺序
2.根据它的特性还可以实现简单的LRU策略等

如下图:
在这里插入图片描述

继承体系

其实看名字就可以知道LinkedHashMap实际底层就是Hashmap,只不过在底层多维护了一个双向链表

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

存储结点Entry

可以看见底层结点就是调用了父类的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);
        }
    }

成员变量

  • head:双链表头
  • tail:双链表尾
  • accessOrder:顺序访问
    //双链表的头(最大的)。
    transient LinkedHashMap.Entry<K,V> head;

    //双链表的尾部(最年轻的)。
    transient LinkedHashMap.Entry<K,V> tail;

    //boolean型,判断是否按访问顺序排序链表
    //默认accessOrder为false,即迭代时的输出顺序就是插入顺序。
    //比如插入1、2、3,迭代输出就是1、2、3;若为true,输出顺序即节点访问顺序。比如插入1、2、3,迭代之前访问了2,又访问了1,迭代出来就是3、2、1,
    //每一次访问之后就将该访问的结点放入双向链表最后
    final boolean accessOrder;

构造方法

5个构造方法

 	
    public LinkedHashMap(int initialCapacity, float loadFactor) {		
    //super调用父类方法,实际上就是hashmap传入初始容量,和负载因子
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }

    
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    //默认构造方法
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }
	//传入map集合
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

    //可以传入accessOrder,accessOrder的意思上面变量那里说明过这里不多说
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

常用方法

get()

实际上就是调用getNode方法传入key的hash值和key值,如果=null则返回null,否则调用afterNodeAccess(就是将访问过的结点加到双链表的末尾),并且返回e的值

    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }

接下来看看getNode()和afterNodeAccess()方法

getNode()

这里getNode方法实际访问的就是Hashmap中的getNode,如果存在则返回该结点,不存在则返回null

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

afterNodeAccess()

这个方法实际上就是将访问了的结点给拿到最后,它的前驱结点和后继结点相连

 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;
            //如果b==null则说明p是头结点,否则将b指向a也就是 p.before=p.after
            if (b == null)
                head = a;
            else
                b.after = a;
             //判断是否是尾结点,然后同上
            if (a != null)
                a.before = b;
            else
                last = b;
                //如果尾结点为null,说明无结点,p就是头点
            if (last == null)
                head = p;
            else {
            //否则将尾结点指向p
                p.before = last;
                last.after = p;
            }
            //然后p变成tail
            tail = p;
            ++modCount;
        }
    }

put()

LinkedHashMap本身没有重写父类的put方法,而是直接使用父类的put方法
这里是HashMap中Putval,不懂的可以看看
HashMap
可以看出put方法调用的父类的put
在这里插入图片描述
如果看过HashMap中putVal源码的朋友肯定知道其中有一个newNode方法,这里LinkedHashMap对其重写

 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;
    }

linkNodeLast:
加入结点之后需要把他放入双向链表的尾部

   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;
        }
    }

remove()

实际上LinkedHashMap删除结点也是直接使用的父类的remove如下图:
在这里插入图片描述
而LinkedhashMap主要重写的是afterNodeRemoval方法
看得出来其实就是把双向链表中的指针去掉


//LinkedHashMap
    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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值