美文网首页
Java集合-LinkedHashMap工作原理

Java集合-LinkedHashMap工作原理

作者: Misout | 来源:发表于2017-08-18 16:36 被阅读810次

    原创作品,转载请注明出处。

    概述

    在了解了Java集合-HashMap源码实现深入解析后,再来看LinkedHashMap就简单的多了。
    先来个示例程序:

    LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
    map.put("语文", "1");
    map.put("数学", "2");
    map.put("英语", "3");
    map.put("历史", "4");
    map.put("政治", "5");
    map.put("地理", "6");
    map.put("生物", "7");
    map.put("化学", "8");
    map.put("物理", "9");
    for(Entry<String, String> entry : map.entrySet()) {
        System.out.println(entry.getKey() + ": " + entry.getValue());
    }
    

    想想输出的结果是什么?结果顺序怎样?

    语文: 1
    数学: 2
    英语: 3
    历史: 4
    政治: 5
    地理: 6
    生物: 7
    化学: 8
    物理: 9
    

    有没有发现,输出结果的顺序和元素插入的顺序一致?而我们再HashMap中插入的顺序和输出的顺序却不一致。Why?要解释这个问题,必须从其数据存储结构说起。

    数据结构

    代码调试观察的变量结果:

    图:LinkedHashMap调试时数据存储的结构图
    根据上述调试结果,画出了如下的结构图: 图:数据结构示意图,蓝色箭头为Entry节点的链接next,绿色和棕色箭头为链表节点的after和before

    再看看源码中的变量声明:

    public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
        /**
         * The head of the doubly linked list.
         */
        private transient Entry<K,V> header;
    
        private final boolean accessOrder;
        // 其他省略
    }
    

    源码中只声明了一个header的Entry节点和accessOrder属性:
    (1)accessOrder:迭代顺序标识,默认为false,按插入顺序迭代,true则按访问顺序迭代。
    (2)header节点:实现链表的关键。是一个Entry节点,该Entry继承自HashMap,采用内部类实现,源码如下:

    private static class Entry<K,V> extends HashMap.Entry<K,V> {
            // These fields comprise the doubly linked list used for iteration.
            Entry<K,V> before, after;
            // 其他省略
    }
    

    因此LinkedHashMap内部的Entry节点除了继承HashMap.Entry的key、value、hash、next引用属性外,还添加了before和after引用节点以支持双向链表。这里只见到了header,没有tail,因此header其实也充当了tail的角色,正如所图中所示,所有元素和header一起形成互连的双向链表结构。到此,就拥有了链表的结构。

    而LinkedHashMap还拥有HashMap一样的结构,原因在于其继承自HashMap,因此HashMap中用于存储元素的table也被继承过来,于是乎元素的存储也具备和HashMap一样的规则,所有元素存储在table中,table下的每个索引在原HashMap的结构下也是一个单向链表,同时每个元素中额外增加的before和after引用将table中的元素互连起来。正如调试代码中看到的那样。

    到此我们就不难理解,为什么默认情况下,我们迭代时的顺序和插入的顺序一致了。因为迭代时,LinkedHashMap总是先从head出发,根据before引用遍历到所有元素,而在链表结构上插入元素总是插入到末尾。

    put方法原理

    LinkedHashMap没有直接重写HashMap的put方法,而是重写了HashMap中put方法用到的addEntry和createEntry方法,方法源码如下:只是多了一个after和before引用链的操作。

        void addEntry(int hash, K key, V value, int bucketIndex) {
            // 先调用HashMap的addEntry方法存到table中
            super.addEntry(hash, key, value, bucketIndex);
    
            // 设置before,after引用,总是放在header的after引用上
            // Remove eldest entry if instructed
            Entry<K,V> eldest = header.after;
            if (removeEldestEntry(eldest)) {
                removeEntryForKey(eldest.key);
            }
        }
    
        void createEntry(int hash, K key, V value, int bucketIndex) {
            HashMap.Entry<K,V> old = table[bucketIndex];
            Entry<K,V> e = new Entry<>(hash, key, value, old);
            table[bucketIndex] = e;
            e.addBefore(header);// 从这行代码可以看出和header建立了关系
            size++;
        }
    

    其中Entry的addBefore方法实现如下,就是把header的after指向新元素,原来的尾部元素也指向新元素:

            private void addBefore(Entry<K,V> existingEntry) {
                after  = existingEntry;
                before = existingEntry.before;
                before.after = this;
                after.before = this;
            }
    

    既然LinkedHashMap继承自HashMap,那么扩容规则也和HashMap一样。

    get方法原理

    LinkedHashMap重新了get方法:

        public V get(Object key) {
            Entry<K,V> e = (Entry<K,V>)getEntry(key);// getEntry用的是父类的方法
            if (e == null)
                return null;
            e.recordAccess(this);
            return e.value;
        }
    

    实现基本和HashMap一致,e.recordAccess(this), 这个调用是设置accessOrder为true时有效的。即按访问顺序读取,这个方法会将最近访问的元素移动到链表末尾,方便下次访问,因此如果设置了这个参数,不管插入还是读取,都会改变链表的结构,在使用时需要谨慎评估。

            void recordAccess(HashMap<K,V> m) {
                LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
                if (lm.accessOrder) {
                    lm.modCount++;
                    remove();
                    addBefore(lm.header);
                }
            }
    

    总结


    1、LinkedHashMap继承于HashMap。
    2、数据存储结构结合了Hash表和链表结构,元素存储用Hash表,元素链接通过继承父类Entry节点并增加before和after引用实现。
    3、LinkedHashMap的迭代顺序收到accessOrder属性的影响,默认为false,按插入顺序访问,true按上次读取顺序访问:即每次访问元素时会把元素移动到链表末尾方便下次访问,结构会时刻变化。


    白天在加班加点的干活,还要抽出时间成为技术知识的分享者,分享不易。

    喜欢我的文章请点赞,欢迎打赏,成为我们坚持的动力。

    相关文章

      网友评论

          本文标题:Java集合-LinkedHashMap工作原理

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