美文网首页
LinkedHashMap源码解析(JDK8)

LinkedHashMap源码解析(JDK8)

作者: 囧囧有神2号 | 来源:发表于2018-07-03 05:22 被阅读0次

    LinkedHashMap继承自HashMap
    public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
    它的数据结构与HashMap的类似,数组+链表+红黑树,不同的是LinkedHashMap的节点

    Entry

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

    多了两个指针,before与after,LinkedHashMap就是利用这两个指针来实现顺序性,这个顺序可以指插入时的顺序:before代表之前于你的节点,after代表之后;也可以代表LRU:此时最新被改动的节点(新插入的,节点value被替换的,被get访问的)将在末尾,头节点代表最少被改动访问的节点,所以LinkedHashMap可用来实现LRUCache。

    Entry继承图
    在分析HashMap源码时,发现static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V>,为什么不直接继承Node,而且在相关方法中也没有用到Entry的before与after指针?因为LinkedHashMap也要使用TreeNode,利用linkNodeLast来调整TreeNode的before与after指针
        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;
        }
    

    成员变量与构造函数

    头节点
        transient LinkedHashMap.Entry<K,V> head;
    尾节点
        transient LinkedHashMap.Entry<K,V> 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<? extends K, ? extends V> 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的设置。

    插入操作

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

    LinkedHashMap重写的newNode方法
        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;
        }
    

    newNode方法再HashMap创建新节点时被调用。

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

    无论是插入顺序还是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情况下将该节点调到末尾,因为它被改动了。

        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;
                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节点。

        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);
            }
        }
    
        protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
            return false;
        }
    

    关于evict

    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方法,其在LinkedHashMap的实现返回的是false,也就是不执行删除头节点的操作。那么这个默认返回false的removeEldestEntry意义在哪?当你要实现LRUCache时可以重写该方法,实现自己的逻辑,例如当节点数查过最大值就删除访问最少的节点。

    get

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

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

    remove

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

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

    来看看

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

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

    迭代器

    Iterator

    主要是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;
            }
    
            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;
            }
        }
    
        final class LinkedKeyIterator extends LinkedHashIterator
            implements Iterator<K> {
            public final K next() { return nextNode().getKey(); }
        }
    
        final class LinkedValueIterator extends LinkedHashIterator
            implements Iterator<V> {
            public final V next() { return nextNode().value; }
        }
    
        final class LinkedEntryIterator extends LinkedHashIterator
            implements Iterator<Map.Entry<K,V>> {
            public final Map.Entry<K,V> next() { return nextNode(); }
        }
    

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

    相关文章

      网友评论

          本文标题:LinkedHashMap源码解析(JDK8)

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