精读JDK源码之LinkedHashMap

256 阅读3分钟

LinkedHashMap继承自HashMap,拥有HashMap的所有特性,再此基础上,还提供两大特性: 1、按照插入顺序进行访问 2、实现访问最少最先删除,相当于LRU淘汰策略

属性

    /**
     * 继承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);
        }
    }

    private static final long serialVersionUID = 3801124242820219131L;

    /**
     * 链表头
     */
    transient LinkedHashMap.Entry<K,V> head;

    /**
     * 链表尾
     */
    transient LinkedHashMap.Entry<K,V> tail;

    /**
     * 两种访问方式,默认false
     * false:按照插入顺序提供访问
     * true:按照访问顺序,把经常访问的值放到队尾
     */
    final boolean accessOrder;

总体来说,LinkedHashMap相当于HashMap和LinkedList结合体,把Map的每个节点串联起来,保证不同元素的顺序性

按照插入顺序访问

按照顺序新增

    //新建节点,并加到链表的尾部
    TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
        TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
        linkNodeLast(p);
        return p;
    }
    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;
        }
    }
    

按照顺序访问

LinkedHashMaP只提供单向访问,可以通过迭代器进行访问。可以对key、value、entity提供迭代方法。

以LinkedHashIterator为例,其代码如下:

    abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;
        LinkedHashMap.Entry<K,V> current;
        int expectedModCount;

        
        LinkedHashIterator() {
            next = head;//头节点作为第一个访问的节点
            expectedModCount = modCount;
            current = null;
        }

        public final boolean hasNext() {
            return next != null;
        }

        final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)//链表为空
                throw new NoSuchElementException();
            current = e;
            next = e.after;//找到下一个节点
            return e;//返回next节点,第一个返回的是头节点
        }

        public final void remove() {
            Node<K,V> p = current;
            if (p == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            current = null;
            K key = p.key;
            removeNode(hash(key), key, null, false, false);
            expectedModCount = modCount;
        }
    }

访问最少删除策略(LRU)

这种策略的意思就是经常访问的元素会被追加到队尾,这样不经常访问的就在对头,可以通过设置删除策略,删除不经常访问的节点

访问方法

    public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)//策略是LRU
            afterNodeAccess(e);//将这个节点移动到队尾
        return e.value;
    }

    void afterNodeAccess(Node<K,V> e) { // move node to last
        LinkedHashMap.Entry<K,V> last;
        if (accessOrder && (last = tail) != e) {//首先保证访问策略是LRU
            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;
        }
    }

删除策略

LinkedHashMap本身没有put方法,调用的是HashMap的put方法(继承hashmap类),但LinkedHashMap实习了put方法中的调用afterNodeInsertion方法,这个方法实现了删除

    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry<K,V> first;
        //removeEldestEntry()来控制删除策略
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            //删除头节点
            removeNode(hash(key), key, null, false, true);
        }
    }