LinkedHashMap 源码分析

为啥要说到这个呢,因为它是实现LRU算法的比较方便的类,同时它的实现也依赖hashmap,而hashmap是Map里面使用最频繁的类。所以分析它非常有意义。
我们可以从LRU算法角度来分析LMM的基础功能。首先温习下LRU淘汰算法:
lru缓存淘汰算法:该算法师根据数据的历史访问记录来进行淘汰数据,思想,如果该数据最近倍访问过,那么将来的访问率也比较高
实现思想:如果该数据已经存在我们的链表中,经过一次遍历,找到到该元素,需要将该数据放到链表的头部
如果该数据不在链表中,此时链表有足够的空间,将该数据放到链表的头部即可,如果该链表的数据已经满了,则删除链表尾的数据.,这腾出存储空间,将该数据放到链表的头部,总之lru核心思想是将最近最少使用的数据放到链表的尾部,如果存储空间不足的时候,淘汰即可,链表的头部总是存放最近使用率最频繁的数据.(转载自:https://blog.csdn.net/w5201314ws6123/article/details/85806213)
那么我们先从初始化来分析吧。

 public LinkedHashMap(
            int initialCapacity, float loadFactor, boolean accessOrder) {
        //直接调用HashMap的构造方法。    
        super(initialCapacity, loadFactor);
        init();
        //accessOrder 如果LRU 算法的话,应该为true。
        this.accessOrder = accessOrder;
    }      

init() {
header = new LinkedEntry<K, V>();
}
初始头部header节点也就是root节点。这是一个双向循环链表,拥有nxt,prev,并且继承自HashMapEntry。
那么我们从put,get,还有迭代方法来分析LinkedHashMap的核心。
JDK 1.8中并没有put的方法是调用的HashMap的put,里面会addNewEntry。所以LLM重写了此方法。

  void addNewEntry(K key, V value, int hash, int index) {
    LinkedEntry<K, V> header = this.header;
     //注意此处,意思是否需要将header.nxt 指向的节点删除,如果不是初始化的header,eldest!=header 会成立。但是removeEldestEntry 这个需要实现LRU算法的子类来实现。其实就是判断,目前的size是否太大,需要删除header.nxt,以便链表能够插入新的Entry.
    // Remove eldest entry if instructed to do so.
    LinkedEntry<K, V> eldest = header.nxt;
    if (eldest != header && removeEldestEntry(eldest)) {
        remove(eldest.key);
    }

    // Create new entry, link it on to list, and put it into table
    LinkedEntry<K, V> oldTail = header.prv;
    LinkedEntry<K, V> newTail = new LinkedEntry<K,V>(
            key, value, hash, table[index], header, oldTail);
   //此处实际上是将newTail插入到header头部。由于是循环链表,old6Tail.nxt= newTail。实际上LRU算法说简单点就是根据访问循序来的,但是实际上插入顺序也是默认的执行。         
    table[index] = oldTail.nxt = header.prv = newTail;
}  

现在看看这个get方法。

public V get(Object key) {
        /*
         * This method is overridden to eliminate the need for a polymorphic
         * invocation in superclass at the expense of code duplication.
         */
        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 = Collections.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((LinkedEntry<K, V>) e);。这个方法会将get到的LinkedEntry 移动到head头部。看方法。

private void makeTail(LinkedEntry<K, V> e) {
         //首先将e的unlik,实际就是先删除e。
        // Unlink e
        e.prv.nxt = e.nxt;
        e.nxt.prv = e.prv;
       //然后将e放入到头部。这个在put里面的算法一样就不分析了。
        // Relink e as tail
        LinkedEntry<K, V> header = this.header;
        LinkedEntry<K, V> oldTail = header.prv;
        e.nxt = header;
        e.prv = oldTail;
        oldTail.nxt = header.prv = e;
        modCount++;
    }

大家可以看到如果accessOrder 为true,get的时候会将get到的值放入头部这么做实际就是LRU算法。也可以叫做缓存算法。这样做的目的其实就是因为缓存容量优先,我们需要把最少使用的item放置到末尾,然后进行删除,这样cache维护的都是一些访问比较频繁的数据。这样当我们需要使用缓存的时候,首先可以直接到cache里面get,由于缓存存储的都是一些教频繁的数据,我们有一定的概率能够get到,那么如果命中那么就直接拿到对象了,不用再new了。这就是LRU算法思想,android开发里面大量使用了这种思想。而LinkedHashMap就是非常方便的实现这种算法的了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值