LruCache源码解析

来源:互联网 发布:imovie mac 教程 编辑:程序博客网 时间:2024/05/21 06:54

最近正在看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
原创粉丝点击