在开发Android过程中,多少会涉及到缓存。例如加载网络图片,不能每次要显示某张网络图片,都要从网络下载,这样的话,不仅浪费用户流量,还可能会造成不好的体验。一般的做法都是先将图片加载到本地保存起来,下次还需要显示同一张图片,直接从内存中获取就行,无需通过网络。这就是缓存了。

我们都知道,Android的内存少的可怜,动不动就OOM,所以缓存需要一定的策略,如LRU,LFU,FOFI。

LRU(Least Recently Used),最近最少使用原则,也就是淘汰最长时间未使用的对象。如缓存的顺序为
A -> B -> C -> D -> A
假如已缓存满了,此时要假如一个E,根据最近最少使用原则,则会淘汰B,那么容器中只剩下ACDE。

我们先看一下怎么使用这个LruCache缓存数据:

int cacheSize = 4 * 1024 * 1024; // 4MiBLruCache bitmapCache = new LruCache(cacheSize) {    protected int sizeOf(String key, Bitmap value) {        return value.getByteCount();    }}}

从源码分析LruCache,从属性看起:

// 保存数据的Mapprivate final LinkedHashMap map;// 当前size,不一定是个数,从上面的例子可以看出,我们可以自定义sizeprivate int size;// 最大size,同上private int maxSize;// put个数private int putCount;// get没击中后,创建个数private int createCount;// 剔除的个数private int evictionCount;// get击中(根据key找到value)个数private int hitCount;// get没击中个数private int missCount;

LruCache使用了LinkedHashMap保存数据。LinkedHashMap有个特性,就是保存的数据按访问顺序来排列的,举个例子就很清楚了:

public class Tester {    private static int MAX=5;    public static void main(String[] args) {        HashMap map = new LinkedHashMap(MAX/2,0.5f,true) {            @Override            protected boolean removeEldestEntry(Map.Entry eldest){                // 如果超过了MAX个,就允许删除最老一个                if(size() > MAX) {                    return true;                }                return false;            }        };        map.put("a", "a");        map.put("b", "b");        map.put("c", "c");        map.put("d", "d");        map.put("e", "e");        map.put("c", "c");        for (Map.Entry entry : map.entrySet()) {              System.out.print(entry.getValue() + ", ");          }          System.out.println();        map.get("b");        for (Map.Entry entry : map.entrySet()) {              System.out.print(entry.getValue() + ", ");          }          System.out.println();        map.put("f", "f");        for (Map.Entry entry : map.entrySet()) {              System.out.print(entry.getValue() + ", ");          }          System.out.println();    }}

打印结果为:

a, b, d, e, c, a, d, e, c, b, d, e, c, b, f,

LruCache正好利用了LinkedHashMap这个特性做到最近最少使用原则的。再看构造函数:

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

设置了最大size,并初始化了LinkedHashMap,我们结合一下LinkedHashMap构造函数了解初始化参数:

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {      super(initialCapacity, loadFactor);      init();      this.accessOrder = accessOrder;  }  

initialCapacity是初始化空间大小
loadFactor是加载因子,就是当他的size大于initialCapacity*loadFactor的时候就会扩容
accessOrder是否按照访问顺序排列数据

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增加新值大小        size += safeSizeOf(key, value);        previous = map.put(key, value);        if (previous != null) {            // 如果之前有值,size减去旧值大小            size -= safeSizeOf(key, previous);        }    }    if (previous != null) {        // 提供子类释放旧的资源        entryRemoved(false, key, previous, value);    }    // 限制大小    trimToSize(maxSize);    return previous;}

put方法先调用了safeSizeOf方法计算要缓存数据的大小:

private int safeSizeOf(K key, V value) {    int result = sizeOf(key, value);    if (result < 0) {        throw new IllegalStateException("Negative size: " + key + "=" + value);    }    return result;}

safeSizeOf调用了sizeOf方法计算:

protected int sizeOf(K key, V value) {   return 1;}

sizeOf方法默认返回1,也就是LruCache默认计算大小的方式是,我们put一个数据,size加1,所以一般我们会重写这个方法,如:

int cacheSize = 4 * 1024 * 1024; // 4MiBLruCache bitmapCache = new LruCache(cacheSize) {    protected int sizeOf(String key, Bitmap value) {        return value.getByteCount();    }}}

我们继续看put方法,计算完大小之后,然后调用map.put(key, value)方法把数据加入到LinkedHashMap中。如果之前有数据,也就是previous不为null,size要减去previous的大小。如果previous不为null,还调用了entryRemoved方法:

protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

竟然是一个空的方法,也就是子类可以重写这个方法。根据官方解释,如果你的缓存值,需要显式释放的资源,你可以调用这个方法。明白了吧!!!继续put方法,最后调用了trimToSize方法:

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;            }            // 如果当前size大于最大size,取出最老(不常)的一个            Map.Entry toEvict = map.eldest();            if (toEvict == null) {                break;            }            key = toEvict.getKey();            value = toEvict.getValue();            // 从LinkedHashMap移除            map.remove(key);            // 减去移除数据的大小            size -= safeSizeOf(key, value);            evictionCount++;        }        entryRemoved(true, key, value, null);    }}

从代码注释的分析来看,trimToSize方法是限制大小,如果当前size超过最大的size,剔除最近最不常用的数据。put方法分析完毕,一般的套路都是,有put就是get,我们看看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.     */    // 尝试根据key创建新的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增加新创建的值大小            size += safeSizeOf(key, createdValue);        }    }    if (mapValue != null) {        entryRemoved(false, key, createdValue, mapValue);        // 返回查找的值        return mapValue;    } else {        // 限制大小        trimToSize(maxSize);        // 返回新创建的值        return createdValue;    }}

先从map中根据key找value,如果value不为空,直接返回value,结束查找。如果没找到,调用create(key)方法尝试创建一个,如果不为null,就保存到map中:

protected V create(K key) {    return null;}

默认返回一个null对象,所以这个方法和entryRemoved方法一样,提供子类重写,意思是,如果我们获取不到key对应的值,重写之后,可以提供一个默认的值,并保存到map中。保存的过程中,会涉及到线程不同问题,还要判断一下,是不是已经有值了(如果你觉得:就是因为找不到value,才创建新的,为何还要判断map的key是否有值,那证明你线程同步还得学习一下)。如果有值,就不把新创建的加入到map中,并返回该值。如果没有值,就把心创建的加入map中,调用trimToSize重新限制大小,并返回新创建的值。

LruCache提供了remove方法删除缓存,代码:

public final V remove(K key) {    if (key == null) {        throw new NullPointerException("key == null");    }    V previous;    synchronized (this) {        previous = map.remove(key);        // 如果删除成功,size减去移除数据的大小        if (previous != null) {            size -= safeSizeOf(key, previous);        }    }    // 提供子类释放资源    if (previous != null) {        entryRemoved(false, key, previous, null);    } // 返回当前移除的对象    return previous;}

还提供了清除所有缓存的方法:

public final void evictAll() {    trimToSize(-1); // -1 will evict 0-sized elements}

LruCache允许调用resize方法重新定义最大size:

public void resize(int maxSize) {    if (maxSize <= 0) {        throw new IllegalArgumentException("maxSize <= 0");    }    synchronized (this) {        this.maxSize = maxSize;    }    trimToSize(maxSize);}

LruCache源码并不多,也比较容易理解。

更多相关文章

  1. Android 中,应用程序需要的图片资源如何针对不同屏幕大小手机设计
  2. 关于“Android SDK manager中不出现完整Android版本安装包列表”
  3. Android关于apk版本更新方法
  4. Android之gallery 常见2种使用方法和3D效果总结
  5. Android中一张图片占用的内存大小
  6. [置顶] android中图片的三级cache策略(内存、文件、网络)之三:文件

随机推荐

  1. android 通过局域网udp广播自动建立socke
  2. Android(安卓)Looper详解
  3. 一定能成功的Android NDK环境配置教程
  4. [置顶] Android(安卓)进程常驻(1)----开篇
  5. Android中的版本适配
  6. 转:Android内存管理机制
  7. Android 之如何优化 UI 渲染(下)
  8. Android(安卓)studio 导入github上的源码
  9. CH340与Android串口通信
  10. Android中各种Adapter的用法