LinkedHashMap 源码剖析

138 阅读4分钟

LinkedHashMap 源码剖析

image.png

image.png 可以看出,LinkedHashMap 的迭代顺序是和插入顺序一致的

image.png

image.png LinkedHashMap 定义了排序模式 accessOrder(boolean 类型,默认为 false),访问顺序则为 true,插入顺序则为 false。

为了实现访问顺序遍历,我们可以使用传入 accessOrder 属性的 LinkedHashMap 构造方法,并将 accessOrder 设置为 true,表示其具备访问有序性。

image.png

image.png

image.png

LRU缓存

题目链接 :146. LRU 缓存

上面我们了解到使用访问顺序,会将访问到的元素放到最后的这个特性,我们很容易联想到LRU缓存

image.png 简易版LRU实现代码

image.png

image.png

image.png LinkedHashMap是一个有序的map,很容易想到它是继承了HashMap。

image.png 我们都知道 HashMap 的 bucket 上的因为冲突转为链表的节点会在符合以下两个条件时会将链表转为红黑树:

当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()方法。这个方法会根据 HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。

LinkedHashMap 是在 HashMap 的基础上为 bucket 上的每一个节点建立一条双向链表,这就使得转为红黑树的树节点也需要具备双向链表节点的特性,即每一个树节点都需要拥有两个引用存储前驱节点和后继节点的地址

image.png

image.png

image.png

image.png 一共五个构造方法

image.png

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

    /**
     * Constructs an empty insertion-ordered {@code LinkedHashMap} instance
     * with the specified initial capacity and a default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity
     * @throws IllegalArgumentException if the initial capacity is negative
     */
    public LinkedHashMap(int initialCapacity) {
        super(initialCapacity);
        accessOrder = false;
    }

    /**
     * Constructs an empty insertion-ordered {@code LinkedHashMap} instance
     * with the default initial capacity (16) and load factor (0.75).
     */
    public LinkedHashMap() {
        super();
        accessOrder = false;
    }

    /**
     * Constructs an insertion-ordered {@code LinkedHashMap} instance with
     * the same mappings as the specified map.  The {@code LinkedHashMap}
     * instance is created with a default load factor (0.75) and an initial
     * capacity sufficient to hold the mappings in the specified map.
     *
     * @param  m the map whose mappings are to be placed in this map
     * @throws NullPointerException if the specified map is null
     */
    public LinkedHashMap(Map<? extends K, ? extends V> m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

    /**
     * Constructs an empty {@code LinkedHashMap} instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - {@code true} for
     *         access-order, {@code false} for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

结构分析清楚了,按流程走,我们就来分析增删改查的代码 :

get方法

image.png 调用父类即 HashMapgetNode 获取键值对,若为空则直接返回。

判断 accessOrder 是否为 true,若为 true 则说明需要保证 LinkedHashMap 的链表访问有序性,执行步骤 3。

调用 LinkedHashMap 重写的 afterNodeAccess 将当前元素添加到链表末尾。

void afterNodeAccess(Node < K, V > e) { // move node to last
    LinkedHashMap.Entry < K, V > last;
    //如果accessOrder 且当前节点不未链表尾节点
    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 指向前驱节点,这个 else其实 没有意义
            last = b;

        //如果last为空,则说明当前链表只有一个节点p,则将head指向p
        if (last == null)
            head = p;
        else {
            //反之让p的前驱指针指向尾节点,再让尾节点的前驱指针指向p,加入尾部
            p.before = last;
            last.after = p;
        }
        //tail指向p,自此将节点p移动到链表末尾
        tail = p;

        ++modCount;
    }
}

image.png

image.png

remove方法

下面的是hashmap中的remove方法的实现

image.png

image.png

image.png LinkedHashMap重写了afterNodeRemoval

image.png

void afterNodeRemoval(Node<K,V> e) { // unlink

    //获取当前节点p、以及e的前驱节点b和后继节点a
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    //将p的前驱和后继指针都设置为null,使其和前驱、后继节点断开联系
        p.before = p.after = null;

    //如果前驱节点为空,则说明当前节点p是链表首节点,让head指针指向后继节点a即可
        if (b == null)
            head = a;
        else
        //如果前驱节点b不为空,则让b直接指向后继节点a
            b.after = a;

    //如果后继节点为空,则说明当前节点p在链表末端,所以直接让tail指针指向前驱节点a即可
        if (a == null)
            tail = b;
        else
        //反之后继节点的前驱指针直接指向前驱节点
            a.before = b;
    }

put方法

自己没有实现,通过继承hashmap的put方法,重写它afterNodeInsertion 方法

image.png

image.png

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
          //略 
    		// 存在就修改
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                 //如果当前的key在map中存在,则调用afterNodeAccess
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
         //调用插入后置方法,该方法被LinkedHashMap重写,不存在就插入
        afterNodeInsertion(evict);
        return null;
    }

image.png

image.png

image.png 当时我们的LRU算法中,我们重写了removeEldestEntry方法

image.png 同时成立,至此-----LRU实现!同时我们也了解了LinkedHashMap源码。