Android LruCache内存缓存实现详解

       Android开发中会遇到图片下载的操作,而图片下载为了不经常访问图片的网络请求,会做三级缓存:内存-文件-网络,同样这种缓存策略也适用其他文件的存储。首先,先拿图片URL去内存中找,如果没有就去文件中找,文件中没有找到,再去异步网络请求。下面,分析总结一下第一级缓存--内存缓存。


       内存缓存会用到LruCache这个类,它是Android3.1所提供的一个缓存类,要想兼容Android3.1以下的版本,可以采用support-v4兼容包中提供的LruCache。

       LruCache是一个泛型类,内部会实例化一个LinkedHashMap对象,它是实现Lru算法的关键。其中,Lru是最近最少使用算法,它的核心思想是当缓存满的时候,会优先淘汰那些最近最少使用的缓存对象。

以下部分代码摘自点击打开链接

new LinkedHashMap<k,v>(0,0.75f,true)这句代码表示,0表示初始容量为零,0.75是加载因子,表示容量达到最大容量的75%时就会把内存增加一半。最后一个参数Boolean类型,它是至关重要的,true表示按访问顺序排序,false表示按插入顺序排序。

我这里写了一个小程序专门研究这两个参数的不同之处。

当设置为true时,

public static final void main(String[] args) {
        LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true);
        map.put(0, 0);
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        map.put(4, 4);
        map.put(5, 5);
        map.put(6, 6);
        map.get(1);
        map.get(2);

        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());

        }
    }

输出的结果为:

0:0
3:3
4:4
5:5
6:6
1:1
2:2

而设置为false时,输出的顺序为:

0:0
1:1
2:2
3:3
4:4
5:5
6:6

有以上结果可以看出,这个设置为true时,如果对一个元素进行了操作(put,get),就会把那个元素放到集合的最后,设置为false时,无论怎么操作,集合元素的顺序都是按照插入的顺序来进行存储的。

到了这里我们可以知道,这个LinkedHashMap正是实现Lru算法的核心之处,当内容容量达到最大值时,只需要移除这个集合的前面的元素直到集合的容量足够存储数据时就可以了。

       LruCache是线程安全的,提供了get()和put()方法来完成缓存的获取和添加操作。使用LruCache类时需要强应用,因为强应用不容易被GC回收。

其中put,get的源码如下:

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

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
                map.put(key, mapValue);
            } else {
                size += safeSizeOf(key, createdValue);
            }
        }

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

      使用LruCache类时,

int maxMemory = (int)Runtime.getRuntime().maxMemory()/1024;//app 可用最大内存数
        int cacheSize = maxMemory/8;

        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {

            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount()/1024;
            }

            @Override
            protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
                super.entryRemoved(evicted, key, oldValue, newValue);
                //如果需要的话,做一些资源回收操作
            }
        };

     其中,sizeof()方法,是用来计算缓存对象大小,单位要与总容量一致。需要重写sizeof方法,是因为原方法中返回值是不准确,不能准确判定value的值是什么类型,所以需要重写该方法。

     除此之外,常用的还有remove方法。





猜你喜欢

转载自blog.csdn.net/duanmulirui/article/details/70139835