美文网首页
LinkedHashMap解析

LinkedHashMap解析

作者: 海之韵Baby | 来源:发表于2018-04-13 14:39 被阅读0次

    前言

    LinkedHashMap看名字就知道是链表结构, LinkedHashMap继承了HashMap, 上篇文章已经了解HashMap的数据结构是数组+单链表, 那么LinkedHashMap的数据结构也是如此吗, 让我们通过源码揭开它的面纱.

    源码分析

    首先看到有两个属性:

         /**
         * The head of the doubly linked list.
         */
        //双向链表的头部
        private transient LinkedHashMapEntry<K,V> header;
         /**
         * The iteration ordering method for this linked hash map: <tt>true</tt>
         * for access-order, <tt>false</tt> for insertion-order.
         */
        //数据元素的排序方法, true:按照访问顺序排序, false:按照插入顺序排序
        private final boolean accessOrder;
    
    • LinkedHashMapEntry
    private static class LinkedHashMapEntry<K,V> extends HashMapEntry<K,V> {
            // These fields comprise the doubly linked list used for iteration.
            LinkedHashMapEntry<K,V> before, after;
    
            LinkedHashMapEntry(int hash, K key, V value, HashMapEntry<K,V> next) {
                super(hash, key, value, next);
            }
    }
    

    上篇文章分析过HashMapEntry是一个单链表, LinkedHashMapEntry继承了HashmapEntry, 内部又维护了一个before和after, 从而形成了一个双链表.

    • 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(m);
            accessOrder = false;
        }
        public LinkedHashMap(int initialCapacity,
                             float loadFactor,
                             boolean accessOrder) {
            super(initialCapacity, loadFactor);
            this.accessOrder = accessOrder;
        }
    

    都是继承了父类HashMap的构造方法, 并初始化了accessOrder.

    @Override
        void init() {
            header = new LinkedHashMapEntry<>(-1, null, null, null);
            header.before = header.after = header;
        }
    

    LinkedHashMap重写了父类HashMap构造方法中的init方法, 初始化header.
    header是一个key和value都为空, 只保留前后引用的特殊节点.
    header.before = header.after = header初始化了一个空的环形链表.

    来了解下LinkedHashMapEntry的结构:

    private static class LinkedHashMapEntry<K,V> extends HashMapEntry<K,V> {
            LinkedHashMapEntry<K,V> before, after;//前后元素
    
            LinkedHashMapEntry(int hash, K key, V value, HashMapEntry<K,V> next) {
                super(hash, key, value, next);
            }
    
            //修改前后元素的指向, 把该元素从双链表中删除
            private void remove() {
                before.after = after;
                after.before = before;
            }
    
            //修改该元素及existingEntry的前后指向, 在existingEntry元素之前插入该元素
            private void addBefore(LinkedHashMapEntry<K,V> existingEntry) {
                after  = existingEntry;
                before = existingEntry.before;
                before.after = this;
                after.before = this;
            }
    
            //记录操作
            void recordAccess(HashMap<K,V> m) {
                LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
                if (lm.accessOrder) {
                    lm.modCount++;
                    remove();
                    addBefore(lm.header);
                }
            }
    
            void recordRemoval(HashMap<K,V> m) {
                remove();
            }
        }
    

    这里accessOrder就起作用了, 如果为true(按访问顺序排列), 就把当前位置的元素删除, 加到链表头部前面(实际位置不变, 只是修改双链表中前后元素指向)

    重写父类的方法

    后面的一些方法都是重写了父类HashMap中的方法, 让我们看下它都重写了哪些方法.

    1. get
    public V get(Object key) {
            LinkedHashMapEntry<K,V> e = (LinkedHashMapEntry<K,V>)getEntry(key);
            if (e == null)
                return null;
            e.recordAccess(this);
            return e.value;
        }
    

    很简单, 就是把父类HashMap中getEntry(key)返回的Entry强转成LinkedHashMapEntry, 最后返回entry的value.
    注意, 这里每次获取key对应的值, 都有访问记录e.recordAccess(this), 这里会判断如果accessOrder为true, 就把该元素放到链表头部前面.

    1. createEntry
      LinkedHashMap没有重写父类的put方法, 但是重写了父类中的addEntry和createEntry两个方法来.
    void createEntry(int hash, K key, V value, int bucketIndex) {
            HashMapEntry<K,V> old = table[bucketIndex];
            LinkedHashMapEntry<K,V> e = new LinkedHashMapEntry<>(hash, key, value, old);
            table[bucketIndex] = e;
            e.addBefore(header);
            size++;
        }
    

    父类HashMap中的createEntry

    void createEntry(int hash, K key, V value, int bucketIndex) {
            HashMapEntry<K,V> e = table[bucketIndex];
            table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
            size++;
        }
    

    经过对比可以发现, LinkedHashMap与HashMap一样, 在数组对应位置上新建了一个Entry, next指向该位置原先的元素, 不同的是LinkedHashMap又把该新建的Entry放到了环形链表头部之前, 因此每次创建的新元素都是放到头部前面, 那头部后面的元素就成了最旧的元素.
    网上发现一个很生动形象的图来表达LinkHashMap的数据结构及插入元素的顺序, 这里盗用一下:


    LinkedHashMap.png
    1. addEntry
    void addEntry(int hash, K key, V value, int bucketIndex) {
            //获取链表头部后面的元素, 及最旧的元素
            LinkedHashMapEntry<K,V> eldest = header.after;
            if (eldest != header) {
                boolean removeEldest;
                size++;
                try {
                    //判断是否移除最旧的元素
                    removeEldest = removeEldestEntry(eldest);
                } finally {
                    size--;
                }
                //为true, 则调用父类HashMap中的removeEntryForKey移除该key对应的元素
                if (removeEldest) {
                    removeEntryForKey(eldest.key);
                }
            }
            //然后调用父类的addEntry方法增加元素
            super.addEntry(hash, key, value, bucketIndex);
        }
    

    addEntry方法中LinkedHashMap在HashMap增加元素之前多加了一步是否删除最旧元素的判断.
    看过hashMap的源码, 我们知道addEntry方法先判断是否需要扩容然后createEntry, createEntry中LinkedHashMap的改变已经看过, 下面看下扩容时LinkedHashMap做的改变.

    1. transfer
      transfer是数组扩充到原来两倍时, 需要把老数组中所有元素移到新数组的操作.
    @Override
        void transfer(HashMapEntry[] newTable) {
            int newCapacity = newTable.length;
            for (LinkedHashMapEntry<K,V> e = header.after; e != header; e = e.after) {
                int index = indexFor(e.hash, newCapacity);
                e.next = newTable[index];
                newTable[index] = e;
            }
        }
    

    对比HashMap的transfer方法.

    void transfer(HashMapEntry[] newTable) {
            int newCapacity = newTable.length;
            for (HashMapEntry<K,V> e : table) {
                while(null != e) {
                    HashMapEntry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                }
            }
        }
    

    经对比不难发现, HashMap是对老数组中每一个位置的单链表上每一个元素重新计算新坐标,重新放位置, 而LinkedHashMap是对环形链表中的每一个元素重新计算新坐标, 重新放位置, 这样就避免了对每一个元素进行判断, 加快了数组扩容的速率.

    1. containsValue
    public boolean containsValue(Object value) {
            // Overridden to take advantage of faster iterator
            if (value==null) {
                for (LinkedHashMapEntry e = header.after; e != header; e = e.after)
                    if (e.value==null)
                        return true;
            } else {
                for (LinkedHashMapEntry e = header.after; e != header; e = e.after)
                    if (value.equals(e.value))
                        return true;
            }
            return false;
        }
    

    对比HashMap的containsValue:

    public boolean containsValue(Object value) {
            if (value == null)
                return containsNullValue();
    
            HashMapEntry[] tab = table;
            for (int i = 0; i < tab.length ; i++)
                for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
                    if (value.equals(e.value))
                        return true;
            return false;
        }
    private boolean containsNullValue() {
            HashMapEntry[] tab = table;
            for (int i = 0; i < tab.length ; i++)
                for (HashMapEntry e = tab[i] ; e != null ; e = e.next)
                    if (e.value == null)
                        return true;
            return false;
        }
    

    同样, LinkedHashMap是对遍历环形链表的元素, 判断是否包含该value的元素, 而HashMap是遍历数组中每个位置单链表上的每一个元素, 判断是否包含该value的元素.

    1. clear
    public void clear() {
            super.clear();
            header.before = header.after = header;
        }
    

    很简单, 就是修改环形链表头部的前后指向.

    1. eldest
    public Map.Entry<K, V> eldest() {
            Entry<K, V> eldest = header.after;
            return eldest != header ? eldest : null;
        }
    

    获取最旧的元素, 也就是环形链表头部后面的元素. 因为每次创建的新元素都是放到头部之前. 如果accessOrder为true, 查看和修改元素时, 也是把元素重新放到头部之前.

    简单总结一下, LinkedHashMap就是在不改变HashMap数据存储结构的基础上又单独维护了一个环形链表. 每次创建元素的时候放到环形链表头部之前, 如果按照访问顺序排列的话, 每次查看或者修改元素也都重新放到头部之前, 这样很容易就能获取到最老的元素.

    相关文章

      网友评论

          本文标题:LinkedHashMap解析

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