java linkedhashmap 原理

linkedhashmap的原理从如下几方面分析

1、继承关系

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>
linkedhashmap继承hashMap,底层的主要存储结构是Hashmap的table,但是linkedhashmap自己扩展了一个header双向链表,下面再说明。


2、关键属性

private transient Entry<K,V> header;<pre name="code" class="java">private final boolean accessOrder;
 

上面两个属性是linkedhashmap扩展的属性,

首先header是一个双向链表,具体体现在Entry结构中,我们知道hashmap的Entry是一个单向链表,只有一个next属性,但是LinkedHashmap继承了hashmap中的Entry,将其变成了一个双向链表结构,Entry下面将会详细说明。

其次就是accessOrder,它决定了linkedhashmap中的元素在遍历的时候的输出顺序,具体在代码中进行说明。

3、继承的类

首先是Entry结构,它继承hashmap的Entry结构,扩展了before和after两个元素属性和addBefore方法,重写了recordAccess方法,就是在这个方法中accessOrder属性的作用体现出来了,若accessOrder为false,即访问元素的时候不会调整该元素;若accessOrder为true,即访问元素的时候会调整该元素。

调整元素的过程:首先删除当前元素,然后将当前元素调整到header之前,即header的before是当前元素。

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


4、迭代器

LinkedHashIterator是一个内部类,重点看一下nextEntry方法和remove方法,nextEntry方法是从header的下一个元素开始的,这个就决定了遍历元素的顺序。另外remove方法使用了LinkedHashMap.this.remove,即在内部类中调用外部类的对象的remove方法,该方法直接使用hashmap的remove方法,这个用法值得重视,然后将lastReturned置为null,即断除header链表和该元素的链接关系。

    private abstract class LinkedHashIterator<T> implements Iterator<T> {
        Entry<K,V> nextEntry    = header.after;
        Entry<K,V> lastReturned = null;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
            return nextEntry != header;
        }

        public void remove() {
            if (lastReturned == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            LinkedHashMap.this.remove(lastReturned.key);
            lastReturned = null;
            expectedModCount = modCount;
        }

        Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (nextEntry == header)
                throw new NoSuchElementException();

            Entry<K,V> e = lastReturned = nextEntry;
            nextEntry = e.after;
            return e;
        }
    }

5、重写hashMap的方法,这个是重点

    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);
        }
    }
上面的方法除了父类的addEntry,还做了是否删除最后元素的判断,这个地方就网上说的LRU算法可以通过重写removeEldestEntry方法来简单实现的关键,但是在LinkedHashmap中该方法是默认返回false的,即永远不会删除末尾的元素。

    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);
        size++;
    }
上面的方法和hashmap中的类似,只是多了一个处理即和header链接起来,具体可以看看Entry的addBefore方法,header是如何和新的Entry关联的。另外从这里我们也可以看出,header实际上是一个辅助的双向链表结构,它不会另外生成新的Entry对象,相当于一个Entry对象生成的时候,不但有table还会有header和它进行关联。

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;
    }
上面的方法和hashmap中的也类似,只是对了一个recordAccess的调用,这个调用就关系到了元素是否要做header中进行调整,具体实现可以回头去看Entry中的实现。

    void transfer(HashMap.Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e = header.after; e != header; e = e.after) {
            if (rehash)
                e.hash = (e.key == null) ? 0 : hash(e.key);
            int index = indexFor(e.hash, newCapacity);
            e.next = newTable[index];
            newTable[index] = e;
        }
    }
最后上面的这个方法是transfer,因为header已经保存了Entry的引用,所以可以直接针对header去遍历,而不需要基于table进行遍历,官方的说法是这样处理效率更高,从实现我们可以看出确实是优化到了只剩下一个for循环了,hashmap中的transfer是for循环中还有while循环。


最后总结:实际上linkedhashmap就是扩展了hashMap的Entry结构,将其变成双向链表,然后新增一个header变量来记录新创建的Entry。新增的元素链接在header.before的位置,当accessOrder属性为true时通过get方法进行调整后的元素也是在header.before的位置。这样的链接方式有两个地方需要注意:

1)由于遍历的时候是从header之后开始的,这样最先插入的元素会最先遍历到,另外如果accessOrder属性为true,则最后使用的元素会最后遍历到,

2)最少使用的元素或者最先插入的元素是header.after元素,这就是为什么在addEntry方法中,当需要remove最后一个元素的时候,给的是header.after。








  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值