美文网首页
LinkedHashMap原理分析

LinkedHashMap原理分析

作者: Java及SpringBoot | 来源:发表于2018-12-17 15:43 被阅读22次

    一、概述

    LinkedHashMap是通过哈希表和链表实现的,它通过维护一个链表来保证对哈希表迭代时的有序性,而这个有序是指键值对插入的顺序。另外,当向哈希表中重复插入某个键的时候,不会影响到原来的有序性。也就是说,假设你插入的键的顺序为1、2、3、4,后来再次插入2,迭代时的顺序还是1、2、3、4,而不会因为后来插入的2变成1、3、4、2。(但其实我们可以改变它的规则,使它变成1、3、4、2)

    LinkedHashMap的实现主要分两部分,一部分是哈希表,另外一部分是链表。哈希表部分继承了HashMap,拥有了HashMap那一套高效的操作,所以我们要看的就是LinkedHashMap中链表的部分,了解它是如何来维护有序性的。

    clip_image002.png

    二、属性

    在看属性之前,我们先来看一下 LinkedHashMap 的声明:

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

    从上面的声明中,我们可以看见 LinkedHashMap 是继承自 HashMap 的,所以它已经从 HashMap 那里继承了与哈希表相关的操作了,那么在 LinkedHashMap 中,它可以专注于链表实现的那部分,所以与链表实现相关的属性如下。

    //LinkedHashMap的链表节点继承了HashMap的节点,而且每个节点都包含了前指针和后指针,所以这里可以看出它是一个双向链表
    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);
        }
    }
    
    //头指针
    transient LinkedHashMap.Entry<K,V> head;
    
    //尾指针
    transient LinkedHashMap.Entry<K,V> tail;
    
    //默认为false。当为true时,表示链表中键值对的顺序与每个键的插入顺序一致,也就是说重复插入键,也会更新顺序
    //简单来说,为false时,就是上面所指的1、2、3、4的情况;为true时,就是1、3、4、2的情况
    final boolean accessOrder;
    

    三、方法

    如果你有仔细看过HashMap源码的话,你会发现HashMap中有如下三个方法:

    // Callbacks to allow LinkedHashMap post-actions
    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }
    

    如果你没有注意到注释的解释的话,你可能会很奇怪为什么会有三个空方法,而且有不少地方还调用过它们。其实这三个方法表示的是在访问、插入、删除某个节点之后,进行一些处理,它们在LinkedHashMap都有各自的实现。LinkedHashMap正是通过重写这三个方法来保证链表的插入、删除的有序性。

    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        //当accessOrder的值为true,且e不是尾节点
        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;
        }
    }
    

    这段代码的意思简洁明了,就是把当前节点e移至链表的尾部。因为使用的是双向链表,所以在尾部插入可以以O(1)的时间复杂度来完成。并且只有当accessOrder设置为true时,才会执行这个操作。在HashMap的putVal方法中,就调用了这个方法。

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

    afterNodeInsertion方法是在哈希表中插入了一个新节点时调用的,它会把链表的头节点删除掉,删除的方式是通过调用HashMap的removeNode方法。想一想,通过afterNodeInsertion方法和afterNodeAccess方法,是不是就可以简单的实现一个基于最近最少使用(LRU)的淘汰策略了?当然,我们还要重写removeEldestEntry方法,因为它默认返回的是false。

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

    这个方法是当HashMap删除一个键值对时调用的,它会把在HashMap中删除的那个键值对一并从链表中删除,保证了哈希表和链表的一致性。

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

    你没看错,LinkedHashMap的get方法就是这么简单,因为它调用的是HashMap的getNode方法来获取结果的。并且,如果你把accessOrder设置为true,那么在获取到值之后,还会调用afterNodeAccess方法。这样是不是就能保证一个LRU的算法了。

    我在LinkedHashMap的源码中没有找到put方法,这就说明了它并没有重写put方法,所以我们调用的put方法其实是HashMap的put方法。因为HashMap的put方法中调用了afterNodeAccess方法和afterNodeInsertion方法,已经足够保证链表的有序性了,所以它也就没有重写put方法了。remove方法也是如此。

    相关文章

      网友评论

          本文标题:LinkedHashMap原理分析

          本文链接:https://www.haomeiwen.com/subject/yqkckqtx.html