• Java集合之LinkedHashMap


    我们在使用HashMap的过程中会发现一个问题,对于这个集合,我们好像没有办法让其按照我们想要的顺序输出。

    针对这个需求,就诞生了LinkedHashMap。

    LinkedHashMap集成了HashMap,然后有两个自己的独特成员:

    private transient Entry<K,V> header;
    private final boolean accessOrder;

    LinkedList是一个双向链表,header是其头部,是一个初始化为-1的节点,这也是一个链表常见的技巧。

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

    而这位accessOrder同学就是主角啦!

    对LinkedHashMap初始化时,可以选择是否初始化这个成员,

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

    如果不初始化,则默认是false,此时保持放入元素先后的顺序不变化,即按照你插入的顺序排列和输出。

    如果选择初始化为true,则按照访问顺序排列。

    从get操作中可以验证这个结论

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

    看一下recordAccess()函数,这是Entry的方法:

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

    如果LinkedHashMap的accessOrder为true,就先remove,然后添加到双向链表的尾部(双向链表header的before节点就是末尾节点),这样就完成了按访问顺序排列

    至于Entry内部的操作,例如添加、删除之类的,就是简单的双向链表操作的方法,直接上代码了,折叠之:

    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;
    
        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }
    
        /**
         * Removes this entry from the linked list.
         */
        private void remove() {
            before.after = after;
            after.before = before;
        }
    
        /**
         * Inserts this entry before the specified existing entry in the list.
         */
        private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
    
        /**
         * This method is invoked by the superclass whenever the value
         * of a pre-existing entry is read by Map.get or modified by Map.set.
         * If the enclosing Map is access-ordered, it moves the entry
         * to the end of the list; otherwise, it does nothing.
         */
        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();
        }
    }
    View Code

     上面看的是访问节点,那么添加节点呢?

    void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex);
    
        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        }
    }

    直接继承HashMap的添加节点方法,但下面又调用了一个删除最老节点的操作,看一下:

    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }

    咦?那这样不是永远都返回false么?哪还有什么意义?

    当然有意义,我们可以重写这个函数,为期设定删除最老节点的条件,例如size()>100之类的。

    到这里,应该就意识到了,这个LinkedHashMap好像可以实现鼎鼎大名的LRU缓存算法吧?答对了,不过这个在以后细说。

    好了,LinkedHashMap的分析暂时就到这里。

  • 相关阅读:
    maven常用仓库
    AD域安装及必要设置
    oracle创建表空间
    javascript弹出模态窗体
    win8.1 AMD 屏幕亮度无法调整
    tomcat优化
    CentOS 6.2 中文
    tomcat之JNDI数据源配置
    eclipse中tomcat配置(待完善)
    Ant打jar包指定MainClass
  • 原文地址:https://www.cnblogs.com/yueyanglou/p/5256146.html
Copyright © 2020-2023  润新知