LruCache源码解析

最近正在看android官方文档,看到Displaying Bitmaps Efficiently部分,正好看到LruCache,所以便想了解LruCache的源码。

正文

先从LruCache类的成员变量

private final LinkedHashMap<K, V> map;

    /** Size of this cache in units. Not necessarily the number of elements. */
    private int size;//已经存储的大小
    private int maxSize;//规定的最大存储空间

    private int putCount;//put的次数
    private int createCount;//create的次数
    private int evictionCount;//回收的次数
    private int hitCount;//命中的次数
    private int missCount;//丢失的次数

LruCache成员变量不多,其中最重要的是size,maxSize和map这三个成员变量,前两个用于缓存大小的管理,后一个则用于存放缓存的内容。LruCache保存一个LinkedHashMap(双链表),每当value被访问的时候,此value就会移动到队列的头部,当cache已满的时候加入新的value时,在队列尾巴的value会被回收。

LruCache的构造函数

public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

传入maxSize即是我们限定的缓存大小,在构造函数中对map进行了初始化,传入的三个参数中最重要的是第三个参数,将accessOrder设置为true。即访问顺序(从近期访问最少到近期访问最多的顺序来保存元素)符合Lru算法。
如果不是很清楚访问顺序和插入顺序,可以看下

http://www.cnblogs.com/yejg1212/archive/2013/04/01/2992921.html

接下来看LruCache的put方法

public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
    //新加进来的value将会被放置在链表的尾部,put方法会返回key对应的原value,若没有则返回null
            previous = map.put(key, value);
    //如果key对应的value被替换,删除原有value的内存大小
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize);
        return previous;
    }

上述代码中,可以发现LruCache的put方法主要是调用了HashMap的put方法

 @Override public V put(K key, V value) {
        if (key == null) {
            return putValueForNullKey(value);
        }

        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                preModify(e);
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
        }

        // No entry for (non-null) key is present; create one
        modCount++;
        if (size++ > threshold) {
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
        }
        addNewEntry(key, value, hash, index);
        return null;
    }

addNewEntry以上的代码主要是判断传进来的key是否已经存在,如果存在则更换对应的value并返回上一个value,如果key不存在,则执行addNewEntry方法。
LinkedHashMap继承自HashMap,LinkedHashMap本身并没有重写put方法,而是通过重写addNewEntry方法实现value的添加。

@Override void addNewEntry(K key, V value, int hash, int index) {
        LinkedEntry<K, V> header = this.header;

        // 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);
        table[index] = oldTail.nxt = header.prv = newTail;
    }

这里写图片描述
这里写图片描述
这里写图片描述
其中红色线段代表nxt,蓝色代表pre(画的丑,不要见怪)

回到LruCache中的put方法,在其中还有一个重要的方法trimToSize(int maxSize)其作用的是移除一直没被调用的项,直到剩余项的数小于请求的大小,如果maxSize传入-1,则清空缓存中的所有对象。

public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }
        //如果有剩余空间,就不用移除
                if (size <= maxSize) {
                    break;
                }
                //离header最近的,即上图中的1
                Map.Entry<K, V> toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//移除
                size -= safeSizeOf(key, value);//删除其占有的内存
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }

将上述的进行总结:往cache中添加value时,会将加入的value添加到双链表的尾部,同时判断当前缓冲的大小是否已经超过了限定的大小,删除最“老”的。

接下来LruCache的get方法:

public final V get(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V mapValue;
        synchronized (this) {
            mapValue = map.get(key);
            if (mapValue != null) {
                hitCount++;
                return mapValue;
            }
            missCount++;
        }

        /*
         * Attempt to create a value. This may take a long time, and the map
         * may be different when create() returns. If a conflicting value was
         * added to the map while create() was working, we leave that value in
         * the map and release the created value.       
         */

        V createdValue = create(key);
        if (createdValue == null) {
            return null;
        }

        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                 //如果mapValue不为空,则撤销上一步的put操作。
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
        //每次新加入对象都需要调用trimToSize方法看是否需要回收
            trimToSize(maxSize);
            return createdValue;
        }
    }

上面代码中出现的create方法需要我们去重写,但基本上不会去重写该方法,因为如果value丢失我们都会重新去获取。
相同的上述代码中最重要的是map.get(),查看LinkedHashMap的get方法。

@Override 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;
        }
    //查询链表中是否已具有该key对应的value,如果有再判读accessOrder是否为true
        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;
    }

put方法的功能就是从链表中获取key对应的value(因为不是解析LinkedHashMap所以不具体说明),这里需要关注的是makeTail方法

 private void makeTail(LinkedEntry<K, V> e) {
        // Unlink e
        e.prv.nxt = e.nxt;
        e.nxt.prv = e.prv;

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

该方法将访问的key的项放到链表的尾部。

总结

可以发现Lru算法的实现是通过LinkedHashMap来实现的,通过将新添加的和最近访问的项放到链表的尾部,当缓存大于限制时,移除头部的项来实现近期最少使用算法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值