java 源码解析(02) LinkedHashMap

来源:互联网 发布:申威26010 知乎 编辑:程序博客网 时间:2024/05/16 13:38

一、总述

LinkedHashMap是一个有序的HashMap。HashMap源码分析请看《java 源码解析(01) HashMap》。

特点:
a、继承于HashMap,基于HashMap实现映射功能
b、增加对key的排序功能
    b1、通过构造参数实现基于插入排序(key先插入的排在前面)
    b2、通过构造参数实现基于访问排序(最后访问排在后面——LRU(Least Recently Used)算法)


LinkedHashMap的键值对存储结构(LinkedEntry)也是基于HashMap的键值对HashMapEntry扩展的
LinkedEntry在HashMapEntry基础上增加了两个属性,用于指向上一个和下一个的属性,这就构成了一个双向链表
注意:这个链表和HashMapEntry在HashMap里面维护的那个链表不一样。
LinkedEntry是基于Key的顺序产生的一个链表,下一个元素引用是LinkedEntry的nxt属性;
HashMapEntry是基于Key的hash地址一样的键值对构成的链表,下一个元素的引用还是HashMapEntry的next属性
也就是一堆元素(LinkedEntry也是HashMapEntry)他们之间关系存在于两种链表
LinkedEntry源码:

    static class LinkedEntry<K, V>extends HashMapEntry<K, V> {        LinkedEntry<K, V> nxt;        LinkedEntry<K, V> prv;        LinkedEntry() { // 用于创建链表头            super(null, null, 0, null);            nxt = prv = this;        }        LinkedEntry(K key, V value, int hash,HashMapEntry<K, V> next, LinkedEntry<K, V> nxt, LinkedEntry<K,V> prv) {            super(key, value, hash, next);            this.nxt = nxt;            this.prv = prv;        }    }

 二、LinkedHashMap的属性含义及作用

    /** 双向链表(其实是一个环形双向链表)的链表头,该指针对应的元素不存储键值对信息 **/    transient LinkedEntry<K, V> header;     /** 是否基于访问排序 **/    private final boolean accessOrder;

三、LinkedHashMap的构造方法

1、带accessOrder的构造方法

其中参数initialCapacity和loadFactor用于构造父类HashMap,而参数accessOrder用于控制排序方式
accessOrder 为true 基于访问排序;为false基于插入排序
   public LinkedHashMap(intinitialCapacity, float loadFactor, boolean accessOrder) {        super(initialCapacity, loadFactor);        init();        this.accessOrder = accessOrder;    }

其中init方法用于初始化链表头

源码:

    void init() {        header = new LinkedEntry<K, V>();    }

2、其他构造方法

这些构造都是构造出基于插入排序的LinkedHashMap,其他参数和HashMap构造方法一样


 四、LinkedHashMap的操作API

1、put方法

该方法使用父类实现,这里只是重写了添加新键值对方法。
步骤:
a、获取出最前面的元素,通过调用removeEldestEntry判断是否删除该元素,如果是直接删除
b、将新元素插入到链表尾
注:由于使用环形双向链表实现,所以链表头的前一个元素就是链表的最后元素,因此直接定位到最后一个位置插入
    void addNewEntry(K key, V value, inthash, int index) {        LinkedEntry<K, V> header =this.header;        LinkedEntry<K, V> eldest =header.nxt;        if (eldest != header &&removeEldestEntry(eldest)) {            remove(eldest.key);        }        LinkedEntry<K, V> oldTail =header.prv;        LinkedEntry<K, V> newTail = newLinkedEntry<K,V>(key, value, hash, table[index], header, oldTail);        table[index] = oldTail.nxt = header.prv= newTail;    }

其中removeEldestEntry方法默认返回false,我们可以通过重写该方法实现true
源码:

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

2、get方法

步骤基本也是和HashMap一样,只是查找到了而且基于访问排序的话,就会调用makeTail方法重新排序

   public V get(Object key) {        if (key == null) {            HashMapEntry<K, V> e =entryForNullKey;            if (e == null)                return null;            if (accessOrder)                makeTail((LinkedEntry<K,V>) e);            return e.value;        }        int hash = secondaryHash(key);        HashMapEntry<K, V>[] tab = table;        for (HashMapEntry<K, V> e =tab[hash & (tab.length - 1)];                e != null; e = e.next) {            K eKey = e.key;            if (eKey == key || (e.hash == hash&& key.equals(eKey))) {                if (accessOrder)                    makeTail((LinkedEntry<K,V>) e);                return e.value;            }        }        return null;    }

makeTail方法:

将该元素转移到链表尾

   private void makeTail(LinkedEntry<K, V> e) {        e.prv.nxt = e.nxt;        e.nxt.prv = e.prv;        LinkedEntry<K, V> header =this.header;        LinkedEntry<K, V> oldTail =header.prv;        e.nxt = header;        e.prv = oldTail;        oldTail.nxt = header.prv = e;        modCount++;    }

3、键值对被修改之前通知方法实现

该操作也会根据是否基于访问排序来重新排序双向链表
源码:

    void preModify(HashMapEntry<K,V> e) {        if (accessOrder) {            makeTail((LinkedEntry<K, V>)e);        }    }

4、键值对配移除之后通知方法实现

从双向链表中删除该元素
源码:

    void postRemove(HashMapEntry<K,V> e) {        LinkedEntry<K, V> le =(LinkedEntry<K, V>) e;        le.prv.nxt = le.nxt;        le.nxt.prv = le.prv;        le.nxt = le.prv = null; // Help the GC(for performance)    }

5、获取Map内部集合数据方法

原理也和HashMap一样,最主要的不同之处就是迭代器的实现
通过双向链表顺序进行迭代
源码:

    private abstract classLinkedHashIterator<T> implements Iterator<T> {        LinkedEntry<K, V> next =header.nxt;        LinkedEntry<K, V> lastReturned =null;        int expectedModCount = modCount;        public final boolean hasNext() {            return next != header;        }        final LinkedEntry<K, V> nextEntry(){            if (modCount != expectedModCount)                throw newConcurrentModificationException();            LinkedEntry<K, V> e = next;            if (e == header)                throw newNoSuchElementException();            next = e.nxt;            return lastReturned = e;        }        public final void remove() {            if (modCount != expectedModCount)                throw newConcurrentModificationException();            if (lastReturned == null)                throw newIllegalStateException();           LinkedHashMap.this.remove(lastReturned.key);            lastReturned = null;            expectedModCount = modCount;        }    }

 五、总结

LinkedHashMap比HashMap多维护了一个双向环形链表



原创粉丝点击