LinkedHashMap源码解析(JDK8)

public class LinkedHashMap extends HashMap implements Map

LinkedHashMap 继承自 HashMap,在 HashMap 基础上,维护一条双向链表,保持遍历顺序和插入顺序一致。除此之外,LinkedHashMap 对访问顺序也提供了相关支持。在一些场景下,该特性很有用,比如缓存。在实现上,LinkedHashMap 很多方法直接继承自 HashMap,仅为维护双向链表覆写了部分方法。所以,要看懂 LinkedHashMap 的源码,需要先看懂 HashMap 的源码。

HashMap源码解析

红黑树算法

HashMap源码解析-红黑树操作

本文重点放在双向链表的维护上:包括链表的建立过程,删除节点的过程,以及访问顺序维护的过程等

Entry

    static class Entry extends HashMap.Node {
        Entry before, after;
        Entry(int hash, K key, V value, Node next) {
            super(hash, key, value, next);
        }
    }

before 与 after 提供了一种视图,从该角度看是一个所有节点按插入顺序排列的双向链表。之前在分析HashMap的红黑树相关操作时说过,每个table[ i ] 位置处的链/树按 next 看则是一个普通的单向链表,按left,right,parent看则是一个二叉树(还有一个prev 与 next 构成双向链,目的是便于链节点的删除操作),而 LinkedHashMap 继承自HashMap 所以对于它来说同时存在三种视图角度。

LinkedHashMap源码解析(JDK8)_第1张图片

LinkedHashMap源码解析(JDK8)_第2张图片

Entry继承图。

LinkedHashMap将具体操作都交给了HashMap,二者直接究竟是如何配合的?

这涉及到LinkedHashMap如何利用HashMap来实现自己的功能,是这样的,在HashMap的插入删除等操作后会调用钩子方法(afterNodeAccess, afterNodeInsertion, afterNodeRemoval),而这些方法的实现就在LinkedHashMap中,这些方法的目的就是操作 before 和 after 指针。LinkedHashMap 重写newNode,newTreeNode 方法,这两个方法是在插入时HashMap构建新节点时调用的,对于重写后的newNode 先是创建LinkedHashMap#Entry节点,之后将其加到 before/after 链的尾部;对于重写后的newTreeNode 先是创建HashMap的TreeNode节点,因为其继承自LinkedHashMap#Entry,所以含有before/after 指针,之后同样加入到链尾。


在分析HashMap源码时,发现static final class TreeNode extends LinkedHashMap.Entry,为什么不直接继承Node,而且在相关方法中也没有用到Entry的before与after指针?

这当然与LinkedHashMap有关,若是TreeNode直接继承自Node,那么对于LinkedHashMap#Entry就要继承自TreeNode,那么对于LinkedHashMap来说它的每个节点都将含有left,right,parent,prev 四个指针,这是没必要的,浪费空间。所以采用 TreeNode 继承LinkedHashMap.Entry的方式,当然对于HashMap,它没有任何关于before/after的操作,对于它来说这是种浪费,但相比较而言采用这种方式更好。而且,对于HashMap来说若 key 的hashCode方法实现的够好的化,指的是分布均匀,那么树将很少出现。

成员变量与构造函数

头节点
    transient LinkedHashMap.Entry head;
尾节点
    transient LinkedHashMap.Entry tail;
true代表LRU顺序;false代表插入顺序
    final boolean accessOrder;

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

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

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

    public LinkedHashMap(Map m) {
        super();
        accessOrder = false;
        putMapEntries(m, false);
    }

    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

可以看到前四个构造器accessOrder都为false,也就是保持插入顺序;最后一个提供了设置accessOrder值的机会。LinkedHashMap的构造函数就是调用HashMap的,再加上accessOrder的设置。head 是最早插入的或是最久未被操作的节点,tail 与其相反。

插入操作

LinkedHashMap并没有put方法,插入操作交给了HashMap,通过重写newNode/newTreeNode方法来创建自己的节点,并对before与after进行操作

LinkedHashMap重写的newNode方法
    Node newNode(int hash, K key, V value, Node e) {
        LinkedHashMap.Entry p =
            new LinkedHashMap.Entry(hash, key, value, e);
        linkNodeLast(p);
        return p;
    }

LinkedHashMap重写的newTreeNode方法
    TreeNode newTreeNode(int hash, K key, V value, Node next) {
        TreeNode p = new TreeNode(hash, key, value, next);
        linkNodeLast(p);
        return p;
    }

HashMap创建新节点时调用newNode/newTreeNode

    private void linkNodeLast(LinkedHashMap.Entry p) {
        LinkedHashMap.Entry last = tail;
        tail = p;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
    }

无论是插入顺序还是LRU顺序,新插入的节点都将被放在末尾。
在HashMap的putVal方法末尾有这两个判断

            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();
        afterNodeInsertion(evict);
        return null;
    }

第一段代码:意思是如果e不为null,代表key重复新value值替换了旧值,afterNodeAccess(e)被调用,该方法在HashMap中为空,在LinkedHashMap实现,作用就是对accessOrder为true情况(LRU顺序)下将该节点调到末尾,因为它被改动了。

    void afterNodeAccess(Node e) { // move node to last
        LinkedHashMap.Entry last;
        if (accessOrder && (last = tail) != e) {
            LinkedHashMap.Entry p =
                (LinkedHashMap.Entry)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节点的前一个节点b与后一个节点a连在一起,将e调到末尾。LRU顺序下,末尾节点代表着最新的节点,意思是要么是新插入的,被更改的,被get访问到的。
第二段代码:插入新节点后,对于LinkedHashMap来说要进行afterNodeInsertion操作,作用是判断是否要删除head节点,这是一个拓展点,你可以重写removeEldestEntry 方法,执行自己的逻辑,比如数量超过某值后插入新值会删除最久未被操作的值,即头节点。

    void afterNodeInsertion(boolean evict) { // possibly remove eldest
        LinkedHashMap.Entry first;
        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
    }

    protected boolean removeEldestEntry(Map.Entry eldest) {
        return false;
    }

关于evict,默认是true

HashMap
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {

在HashMap的put方法里,调用putVal传的是true,那么决定是否删除head节点的操作就取决于removeEldestEntry方法。

get

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

具体实现在HashMap的getNode方法里,可以看到若是LRU顺序,则被访问的节点会被放入到末尾。

remove

具体操作仍然在HashMap里实现,同putVal一样,留了一个钩子

 final Node removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
................
                afterNodeRemoval(node);
................

来看看

    void afterNodeRemoval(Node e) { // unlink
        LinkedHashMap.Entry p =
            (LinkedHashMap.Entry)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;
    }

就是将该节点的前后连在一起,链表的删除操作。

迭代器

LinkedHashMap源码解析(JDK8)_第3张图片

Iterator

 

主要是LinkedHashIterator

    abstract class LinkedHashIterator {
        LinkedHashMap.Entry next;
        LinkedHashMap.Entry current;
        int expectedModCount;

        LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
        }

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

        final LinkedHashMap.Entry nextNode() {
            LinkedHashMap.Entry e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after;
            return e;
        }

        public final void remove() {
            Node 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;
        }
    }

    final class LinkedKeyIterator extends LinkedHashIterator
        implements Iterator {
        public final K next() { return nextNode().getKey(); }
    }

    final class LinkedValueIterator extends LinkedHashIterator
        implements Iterator {
        public final V next() { return nextNode().value; }
    }

    final class LinkedEntryIterator extends LinkedHashIterator
        implements Iterator> {
        public final Map.Entry next() { return nextNode(); }
    }

各自实现自己的next()方法。

你可能感兴趣的:(集合)