Android 缓存类LruCache源码分析

1.引言

Android开发难免会遇到加载网络图片问题,而加载网络图片难免会遇到多次重复请求加载同一张图片问题,这么一来就导致多次加载网络,不仅浪费资源而且给用户体验感觉加载图片慢。从Android3.1开始,API中多了一个LruCache类,该类是一个使用最近最少使用算法的缓存类。也就是可以把上次下载的图片以键值对的方式保存在缓存中,以便下载加载同一张图片时无须再次从网络上下载,而且加载速度快。

2.LruCache源码详解

这里我把LruCache源码贴出来,几乎每行代码的注释都有,很详细,有意者可以仔细跟着注释阅读源码,以便理解LruCache类的原理。

/** 1.从类名LruCache就知道,该类的作用是一个最近最少使用算法来维护的一个缓存类。 2.该类是一个用于缓存一定数量的值,并且该缓存对象是持有强引用。 3.每当成功get一次值时,该值都会移动到链表的头部,以便标记该值为最近最新的值。 4.当缓存满了时,链表尾部的值会被认为是最近最少使用的值,会被从链表中移除,以便缓存有空间保存新的值。 5.缓存中链表的值发生改变时会调用空方法entryRemoved,开发者可以重写该方法,以便做相应的操作。 6.开发者应该去重写该类中sizeOf方法,该方法返回每个key对应value值得大小,以便该类去维护缓存的大小。 7.该类是一个安全类。同时该类不允许key和value为空。该类在Android3.1之后添加到源码中。 **/
package android.util;

import java.util.LinkedHashMap;
import java.util.Map;


public class LruCache<K, V> {
    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;//添加记录次数
    private int createCount;//创建次数
    private int evictionCount;//移除次数
    private int hitCount;//命中次数
    private int missCount;//未命中次数

    /** * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maximum number of entries in the cache. For all other caches, * this is the maximum sum of the sizes of the entries in this cache. */
    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);//创建链表缓存,该链表缓存是整个LruCache类的重点。
    }

    /** * Sets the size of the cache. * @param maxSize The new maximum size. *重新设置缓存大小 * @hide */
    public void resize(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }

        synchronized (this) {
            this.maxSize = maxSize;
        }
        trimToSize(maxSize);
    }

    /** * Returns the value for {@code key} if it exists in the cache or can be * created by {@code #create}. If a value was returned, it is moved to the * head of the queue. This returns null if a value is not cached and cannot * be created. * 根据key值获得缓存中对应的数据,该方法是线程安全的。 */
    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);//创建新的数据,默认返回null,创建的时候可以重写该方法,以便未命中的时候创建一个新的数据添加到缓存链表中。
        if (createdValue == null) {
            return null;//未命中时默认到这里结束。
        }

        //以下代码是在未命中时创建新数据添加到链表缓存中。
        synchronized (this) {
            createCount++;//标记创建新数据的次数
            mapValue = map.put(key, createdValue);
            //如果链表中已有该key对应的值,则最后取消添加新创建的值。很多人可能感到奇怪,此时链表中应该不存在key对应的值啊?其实这里是为了防止多线程操作导致数据不同步而添加的安全代码。不懂得自己体会去吧!
            if (mapValue != null) {
                // There was a conflict so undo that last put
                map.put(key, mapValue);
            } else {
                //标记已经使用了的内存大小。
                size += safeSizeOf(key, createdValue);
            }
        }

        if (mapValue != null) {
            //链表中发生数据变化时(调用了put,get方法)调用该方法。该方法默认是个空,开发者可以重写该方法在链表中数据变化时做相应的操作。
            entryRemoved(false, key, createdValue, mapValue);
            return mapValue;
        } else {
            //重新整理当前链表维护的内存。
            trimToSize(maxSize);
            return createdValue;
        }
    }

    /** * Caches {@code value} for {@code key}. The value is moved to the head of * the queue. * * @return the previous value mapped by {@code key}. * 添加新的键值对到链表中 */
    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) {//链表中已存在key对应的值则替换原来的值
                size -= safeSizeOf(key, previous);//标记已使用的内存大小
            }
        }

        if (previous != null) {
            //链表中数据发生交换时调用该方法。
            entryRemoved(false, key, previous, value);
        }
        //整理链表维护的内存大小
        trimToSize(maxSize);
        return previous;
    }

    /** * @param maxSize the maximum size of the cache before returning. May be -1 * to evict even 0-sized elements. * 管理链表中内存的大小 */
    private 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;
                }

                // BEGIN LAYOUTLIB CHANGE
                // get the last item in the linked list.
                // This is not efficient, the goal here is to minimize the changes
                // compared to the platform version.
                Map.Entry<K, V> toEvict = null;
                //for循环得到链表末尾的数据,然后移除它。
                for (Map.Entry<K, V> entry : map.entrySet()) {
                    toEvict = entry;
                }
                // END LAYOUTLIB CHANGE

                if (toEvict == null) {
                    break;
                }
                //移除链表末尾的一个数据,该数据就是最近最少使用到的数据。
                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//移除数据
                size -= safeSizeOf(key, value);//重写计算已使用的内存大小
                evictionCount++;//标记移除数据的次数
            }
            //开发者可以重新改方法,当移除数据时做相应的操作。
            entryRemoved(true, key, value, null);
        }
    }

    /** * Removes the entry for {@code key} if it exists. * * @return the previous value mapped by {@code key}. * 从链表中移除数据的方法 */
    public final V remove(K key) {
        if (key == null) {
            throw new NullPointerException("key == null");
        }

        V previous;
        synchronized (this) {
            previous = map.remove(key);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

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

        return previous;
    }

    /** * Called for entries that have been evicted or removed. This method is * invoked when a value is evicted to make space, removed by a call to * {@link #remove}, or replaced by a call to {@link #put}. The default * implementation does nothing. * * <p>The method is called without synchronization: other threads may * access the cache while this method is executing. * * @param evicted true if the entry is being removed to make space, false * if the removal was caused by a {@link #put} or {@link #remove}. * @param newValue the new value for {@code key}, if it exists. If non-null, * this removal was caused by a {@link #put}. Otherwise it was caused by * an eviction or a {@link #remove}. */
    //该方法默认是一个空方法。当链表中发生数据变化时调用该方法,所以开发者可以重写该方法,以便做相应的操作。
    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

    /** * Called after a cache miss to compute a value for the corresponding key. * Returns the computed value or null if no value can be computed. The * default implementation returns null. * * <p>The method is called without synchronization: other threads may * access the cache while this method is executing. * * <p>If a value for {@code key} exists in the cache when this method * returns, the created value will be released with {@link #entryRemoved} * and discarded. This can occur when multiple threads request the same key * at the same time (causing multiple values to be created), or when one * thread calls {@link #put} while another is creating a value for the same * key. */
    //该方法默认返回空,开发者可以重新该方法以便在get链表中的数据失败时创建新的数据添加到链表中。
    protected V create(K key) {
        return null;
    }

    //该方法是重写计算链表中已经使用了的内存大小
    private int safeSizeOf(K key, V value) {
        int result = sizeOf(key, value);
        if (result < 0) {
            throw new IllegalStateException("Negative size: " + key + "=" + value);
        }
        return result;
    }

    /** * Returns the size of the entry for {@code key} and {@code value} in * user-defined units. The default implementation returns 1 so that size * is the number of entries and max size is the maximum number of entries. * * <p>An entry's size must not change while it is in the cache. */
    //该方法默认返回1,开发者必须重写该方法,用来计算每个key对应value值得大小,以便维护链表中缓存的大小。
    protected int sizeOf(K key, V value) {
        return 1;
    }

    /** * Clear the cache, calling {@link #entryRemoved} on each removed entry. */
    //移除链表中所有的数据
    public final void evictAll() {
        trimToSize(-1); // -1 will evict 0-sized elements
    }

    /** * For caches that do not override {@link #sizeOf}, this returns the number * of entries in the cache. For all other caches, this returns the sum of * the sizes of the entries in this cache. * 得到已经使用缓存中内存的大小 */
    public synchronized final int size() {
        return size;
    }

    /** * For caches that do not override {@link #sizeOf}, this returns the maximum * number of entries in the cache. For all other caches, this returns the * maximum sum of the sizes of the entries in this cache. * 得到分配给链表缓存的大小 */
    public synchronized final int maxSize() {
        return maxSize;
    }

    /** * Returns the number of times {@link #get} returned a value that was * already present in the cache. * 得到命中的次数 */
    public synchronized final int hitCount() {
        return hitCount;
    }

    /** * Returns the number of times {@link #get} returned null or required a new * value to be created. * 得到未命中的次数 */
    public synchronized final int missCount() {
        return missCount;
    }

    /** * Returns the number of times {@link #create(Object)} returned a value. * 得到创建新数据的次数 */
    public synchronized final int createCount() {
        return createCount;
    }

    /** * Returns the number of times {@link #put} was called. * 得到添加新数据次数 */
    public synchronized final int putCount() {
        return putCount;
    }

    /** * Returns the number of values that have been evicted. * 得到移除数据的次数 */
    public synchronized final int evictionCount() {
        return evictionCount;
    }

    /** * Returns a copy of the current contents of the cache, ordered from least * recently accessed to most recently accessed. * 得到链表对象 */
    public synchronized final Map<K, V> snapshot() {
        return new LinkedHashMap<K, V>(map);
    }

    @Override public synchronized final String toString() {
        int accesses = hitCount + missCount;
        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
                maxSize, hitCount, missCount, hitPercent);
    }
}

LruCache图片使用示例

一下代码是图片缓存类ImageCache示例代码:

import android.graphics.Bitmap;
import android.util.LruCache;

/** * Created by xjp on 2016/4/2. */
public class ImageCache {
    private LruCache mLruCache;
    private static ImageCache instance = new ImageCache();

    public ImageCache() {
        //得到当前应用总的内存大小
        int appTotalCache = (int) Runtime.getRuntime().totalMemory();
        //取当前应用内存的1/8作为缓存大小
        int maxSize = appTotalCache / 8;
        mLruCache = new LruCache<String, Bitmap>(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //此处返回每个Bitmap对象的大小。值得注意:此处返回值并不是图片的张数,
                // 且返回值的单位应该和maxSize的单位一样。也就是maxSize单位是B,那么此处返回值单位也是B
                return value.getByteCount();
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                //如果缓存中的值被移除,则去回收Bitmap内存。
                if (evicted) {
                    if (oldValue != null && !oldValue.isRecycled()) {
                        oldValue.recycle();
                    }
                }
            }
        };
    }

    public static ImageCache getInstance() {
        return instance;
    }

    //从缓存中读取数据
    public Bitmap getBitmapFromCache(String key) {
        if (mLruCache != null) {
            mLruCache.get(key);
        }
        return null;
    }

    //保存数据到缓存中
    public void putBitmapToCache(String key, Bitmap value) {
        if (mLruCache != null) {
            if (mLruCache.get(key) == null) {
                mLruCache.put(key, value);
            }
        }
    }

    //清除缓存中所有数据
    public void clearCache() {
        if (mLruCache != null) {
            mLruCache.evictAll();
        }
    }
}

总结:以后就可以直接用ImageCache类来作为图片缓存了。

你可能感兴趣的:(源码,图片缓存,LruCache,LruCache源码)