LruCache源码浅析

前言:自从Andorid3.1之后,谷歌引入了LruCache,官方文档说明如下:

 * A cache that holds strong references to a limited number of values. Each time
 * a value is accessed, it is moved to the head of a queue. When a value is
 * added to a full cache, the value at the end of that queue is evicted and may
 * become eligible for garbage collection.
 *
 * 

If your cached values hold resources that need to be explicitly released, * override {@link #entryRemoved}. * *

If a cache miss should be computed on demand for the corresponding keys, * override {@link #create}. This simplifies the calling code, allowing it to * assume a value will always be returned, even when there's a cache miss. * *

By default, the cache size is measured in the number of entries. Override * {@link #sizeOf} to size the cache in different units. For example, this cache * *

This class does not allow null to be used as a key or value. A return * value of null from {@link #get}, {@link #put} or {@link #remove} is * unambiguous: the key was not in the cache. *

  • 官方文档指出几点:
    --hold strong reference: 该缓存引用的是强引用。
    --a value is accessed, it is moved to the head of a queue :每次一个数据被访问,就会被移到对头
    --要重载sizeof用于测量当前内存大小
    --不支持空的key。

LRU:什么是LRU算法? LRU是Least Recently Used的缩写,即最近最少使用
LruCache里面最重要的是维护了一个双向链表(该链表支持有序插入)用于存放缓存数据,LinkedHashMap底层正是实现了LRU算法.

    private final LinkedHashMap map;

LinkedHashMap :

  • LinkedHashMap is an implementation of {@link Map} that guarantees > iteration order.
  • All optional operations are supported.
  • All elements are permitted as keys or values, including null.

  • Entries are kept in a doubly-linked list. The iteration order is, by > > > default, theorder in which keys were inserted. Reinserting an already-present key > doesn't change theorder. If the three argument constructor is used, and {@code accessOrder} is specified as
    {@code true}, the iteration will be in the order that entries were accessed.

  • The access order is affected by {@code put}, {@code get}, and {@code putAll} operations,
  • but not by operations on the collection views.

LinkedHashMap 继承HashMap,里面维护一个内部类LinkeEntry双向链表用于存储数据。
具体分析可以看该博客:图解LinkedHashMap原理


我们先开LruCache的构造函数

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

构造了一个LinkedHashMap,而且第三个参数为true,表示访问有序(这个是实现该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);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

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

        trimToSize(maxSize);
        return previous;
    }

分析:
每次放入一个数据,size会根据当前的数据大小增加

    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);//该方法就是上面说的要重写的方法
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

然后将数据放入map,返回一个previous,其中,map.put(key, value):
HashMap.java

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

        int hash = Collections.secondaryHash(key);
        HashMapEntry[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry 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;
    }

当当前的key在map里面是有的时候,返回旧的数据,并且被回收掉。

if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

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

public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                //如果重载的sizeof方法返回的大小单位跟max传入的大小单位不同,这里会报错
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize) {
                    break;
                }

                Map.Entry 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);
        }

对缓存进行内存裁剪,不断第将队尾的数据删除,直到size<=maxSize.

其中最关键的就是map.eldest()这段代码,拿到当前最老的数据,也就是说最近最少访问的数据。

    public Entry eldest() {
        LinkedEntry eldest = header.nxt;
        return eldest != header ? eldest : null;
    }

由于在构造LinkedMap的时候,构造参数accessOrder是true,也就是说当前的链表的顺序是根据访问时间去定当前数据的位置,越久没访问,越前。

LinkedHashMap:
    /**
     * Relinks the given entry to the tail of the list. Under access ordering,
     * this method is invoked whenever the value of a  pre-existing entry is
     * read by Map.get or modified by Map.put.
     */
    private void makeTail(LinkedEntry e) {
        // Unlink e
        e.prv.nxt = e.nxt;
        e.nxt.prv = e.prv;

        // Relink e as tail
        LinkedEntry header = this.header;
        LinkedEntry oldTail = header.prv;
        e.nxt = header;
        e.prv = oldTail;
        oldTail.nxt = header.prv = e;
        modCount++;
    }

上面就是每次调整数据顺序的代码。

--在如果做图片处理的时候,可以复写entryRemoved对从map里面移除的图片进行复用内存。

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

      //下面代码跟put的代码差不多,也是新建一个value放入map
        synchronized (this) {
            createCount++;
            mapValue = map.put(key, createdValue);

            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            trimToSize(maxSize);
            return createdValue;
        }
    }

该方法有个特殊的地方就是,如果map里面没有对应的key的数据或者出于其他原因key对应的数据被删除了,提供了create(key)方法给用于去重新创建对应的数据。

LruCache

你可能感兴趣的:(LruCache源码浅析)