java 源码解析(02) LinkedHashMap

一、总述

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多维护了一个双向环形链表



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值