jdk源码分析之LinkedHashMap

基本原理

LinkedHashMap继承自HashMap,因此具有HashMap的所有特性。在HashMap的基础上,保持了key的插入顺序或者访问顺序。实现方法就是用双向循环链表将所有的entry链接起来,读取数据的时候直接读取此双向循环链表的数据即可。

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;

    /**
     * The iteration ordering method for this linked hash map: 
     * true for access-order,false for insertion-order.
     */
    private final boolean accessOrder;

header是双向循环链表的头结点,不存储数据,用于定位头尾节点
header.after指向链表的第一个节点
header.before指向链表的最后一个节点
accessOrder是一个标志位,true的话表示按照访问顺序访问链表,可据此构建LRU缓存,fasle的话表示按照插入顺序访问链表

双向循环链表的Entry节点

LinkedHashMap的Entry继承自HashMap的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;

因此LinkedHashMap的Entry节点具有三个指针域,next指针维护Hash桶中冲突key的链表,before和after维护双向循环链表
为了维护双向循环链表,Entry新增加了4个方法

删除节点remove

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

添加节点同样是指针操作,非常高效方便

记录访问recordAccess

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

当LinkedHashMap的标志位accessOrder为true时,标志着要采用访问顺序访问链表。

 remove();
 addBefore(lm.header);

最新访问的节点先从原来的位置删除,然后重现添加到链表的末尾,这样最近最少访问的节点就被挪到了链表的前端。

LinkedHashMap添加数据

LinkedHashMap本质上也是一个HashMap,在HashMap的基础上添加所有entry构成双向循环链表的功能。因此LinkedHashMap并没有覆写HashMap的put方法,只是覆写了HashMap中put方法调用的addEntry方法。
先来回顾下HashMap的put方法够干了什么

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

HashMap的put方法主要功能是计算对应key的桶的位置,遍历桶中链表,找到对应key的entry修改原数据。遍历链表结束没有找到对应key的entry则调用addEntry方法将新添加的键值对entry添加到桶中entry链表的头部。
而对于LinkedHashMap中的entry节点来说,要维持两个链表,一个是桶中的next指针域链接的hask冲突的key构成的entry单链表,第二个就是维护所有entry构成的双向循环链表
LinkedHashMap的addEntry方法如下所示

    void addEntry(int hash, K key, V value, int bucketIndex) {
        createEntry(hash, key, value, bucketIndex);

        // Remove eldest entry if instructed, else grow capacity if appropriate
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length);
        }
    }

先调用createEntry把新节点链接到两条链表上,然后判断是否要删除最近最久未访问的节点(也就是双向循环链表的第一个节点),要删除的话就不需要检查是否需要扩容了,都则要检查是否需要扩容。

    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
    Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }

可见新节点被两条链表都链接上了
第一条是单项非循环链表,桶中hash值冲突得key构成的entry链表

        HashMap.Entry<K,V> old = table[bucketIndex];
    Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
        table[bucketIndex] = e;

第二条是所有entry节点构成的双向循环链表

        e.addBefore(header);

containsValue方法直接在双向循环链表中查找值

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

根据传入参数value是否为null,对双向循环链表进行遍历查找

猜你喜欢

转载自blog.csdn.net/shihui512/article/details/51564344