Android缓存-LruCache分析
2015-08-17 14:52
399 查看
缓存虽然会占据一定的应用内存或者磁盘空间,却允许你以更快的速度获得你需要的对象。因此缓存避免了重复加载,可以提高应用的响应速度。Android中提供了LruCache类来支持缓存。这篇文章着重于对LruCache源码的分析。
如果在缓存队列中存在一些需要显性释放资源的对象时,需要重写entryRemoved方法。
如果缓存miss时,需要根据相应的key来产生对应的value时,需要重写create方法。
默认地,cache大小为缓存队列中的元素个数。然而,在实际运用中,cache大小与缓存队列中的对象大小有关。因此,需要重写sizeOf来改变这一点。
此类是线程安全的,通过synchronized关键字来限制put、get、remove操作。
LruCache不允许key或者value为空,否则将抛出异常-NullPointerException(“key == null”)。
map为缓存队列,LinkedHashMap内的队列有两种维护顺序的方式,一种是根据插入的顺序,新插入的元素将在尾部;另外一种是根据访问的顺序,新访问的元素将在尾部。LruCache中的LinkedHashMap使用的是根据访问顺序来实现LRU(最近最少访问)的。
size不是缓存队列中元素的个数,而是缓存队列中元素的大小,例如一个Bitmap所占的字节数;maxSize是缓存的最大空间。
putCount记录了队列中添加元素的次数;有时,缓存miss时,需要根据相应的key来产生value,createCount负责统计次数;evictionCount则是记录了踢出队列元素的个数;hitCount和missCount分别表示缓存hit和miss的次数。
如果没有重写sizeOf方法,maxSize指的是队列中元素的最大数目;否则,maxSize指的是cache的最大空间。
this.map = new LinkedHashMap
trimToSize(int maxSize)方法是用来限制缓存队列大小到maxSize的方法。从代码中可以看得出,如果现在的size大于maxSize的话,将移除最不常访问的。当参数传入-1时,表示移除所有元素。
从代码中,我们可以知道:
如果缓存队列中存在此key的话,直接返回key相应的value;
如果不存在的话,会尝试通过create方法来创建此value。create方法前面已经说过,就是如果缓存miss时,需要根据相应的key来产生对应的value时,需要重写create方法。由于create方法可能消耗一定的时间,因此在create返回之前,map内可能已经有这个key了,这时就会发生冲突,因此我们会遗弃create产生的value。
put方法中,如果key或value为空,返回空指针异常。如果原本有key存在,需要显性的释放资源。添加成功后,需要调用trimToSize方法避免size超过最大大小。
这个方法也比较简单,移除成功的话,需要释放资源。
如果缓存踢出对象需要显性释放资源的话,需要重写这个方法。
evicted为true的话,说明是由于空间不足造成的;为false的话,说明是由于put或者remove造成的。
如果缓存miss的话,需要根据对应的key产生value的话,需要重写此方法。
返回某个元素的大小,在下面方法的基础上,添加了不为负数的判断。
返回某个元素的大小,默认返回的元素大小为元素的数目1,一般需要重写。
清空缓存队列。
到此为止。
类的介绍
LruCache持有一定数量对象的引用来避免这些对象被回收。当一个对象被访问时,将会被移到缓存队列的头部;当一个对象添加到已经满的队列时,队列的尾部对象(最不常访问)的对象将被踢出队列,意味着允许被垃圾回收器所回收。如果在缓存队列中存在一些需要显性释放资源的对象时,需要重写entryRemoved方法。
如果缓存miss时,需要根据相应的key来产生对应的value时,需要重写create方法。
默认地,cache大小为缓存队列中的元素个数。然而,在实际运用中,cache大小与缓存队列中的对象大小有关。因此,需要重写sizeOf来改变这一点。
此类是线程安全的,通过synchronized关键字来限制put、get、remove操作。
LruCache不允许key或者value为空,否则将抛出异常-NullPointerException(“key == null”)。
变量介绍
[code] 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;
map为缓存队列,LinkedHashMap内的队列有两种维护顺序的方式,一种是根据插入的顺序,新插入的元素将在尾部;另外一种是根据访问的顺序,新访问的元素将在尾部。LruCache中的LinkedHashMap使用的是根据访问顺序来实现LRU(最近最少访问)的。
size不是缓存队列中元素的个数,而是缓存队列中元素的大小,例如一个Bitmap所占的字节数;maxSize是缓存的最大空间。
putCount记录了队列中添加元素的次数;有时,缓存miss时,需要根据相应的key来产生value,createCount负责统计次数;evictionCount则是记录了踢出队列元素的个数;hitCount和missCount分别表示缓存hit和miss的次数。
构造函数
[code] /** * @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); }
如果没有重写sizeOf方法,maxSize指的是队列中元素的最大数目;否则,maxSize指的是cache的最大空间。
this.map = new LinkedHashMap
trimToSize
[code] 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; } 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); } }
trimToSize(int maxSize)方法是用来限制缓存队列大小到maxSize的方法。从代码中可以看得出,如果现在的size大于maxSize的话,将移除最不常访问的。当参数传入-1时,表示移除所有元素。
get方法
[code] /** * 如果存在或者能通过create方法生成的话,返回key对应的value * 如果有value返回的话,它将会被移动到缓存队列的尾部. * 当缓存miss并且不能通过create方法生成时,返回null */ 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++; } /* * 尝试生成value,这可能会耗费一定的时间,在这段时间内,map可能有所变化,因此同一个key可能有 * 冲突,如果冲突发生,我们将遗弃通过create生成的value */ V createdValue = create(key); if (createdValue == null) { return null; } synchronized (this) { createCount++; mapValue = map.put(key, createdValue); if (mapValue != null) { // 有冲突,将mapValue重新存入 map.put(key, mapValue); } else { size += safeSizeOf(key, createdValue); } } if (mapValue != null) { // 显性释放资源 entryRemoved(false, key, createdValue, mapValue); return mapValue; } else { // 由于put了createdValue,验证是否大小超过maxSize trimToSize(maxSize); return createdValue; } }
从代码中,我们可以知道:
如果缓存队列中存在此key的话,直接返回key相应的value;
如果不存在的话,会尝试通过create方法来创建此value。create方法前面已经说过,就是如果缓存miss时,需要根据相应的key来产生对应的value时,需要重写create方法。由于create方法可能消耗一定的时间,因此在create返回之前,map内可能已经有这个key了,这时就会发生冲突,因此我们会遗弃create产生的value。
put方法
[code] 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; }
put方法中,如果key或value为空,返回空指针异常。如果原本有key存在,需要显性的释放资源。添加成功后,需要调用trimToSize方法避免size超过最大大小。
remove方法
[code] 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; }
这个方法也比较简单,移除成功的话,需要释放资源。
其他方法
[code] protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
如果缓存踢出对象需要显性释放资源的话,需要重写这个方法。
evicted为true的话,说明是由于空间不足造成的;为false的话,说明是由于put或者remove造成的。
[code] protected V create(K key) { return null; }
如果缓存miss的话,需要根据对应的key产生value的话,需要重写此方法。
[code] private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } return result; }
返回某个元素的大小,在下面方法的基础上,添加了不为负数的判断。
[code] protected int sizeOf(K key, V value) { return 1; }
返回某个元素的大小,默认返回的元素大小为元素的数目1,一般需要重写。
[code] public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements }
清空缓存队列。
到此为止。
相关文章推荐
- Android中实现短信验证码自动填入
- Android简介
- Android Hook神器:XPosed入门与登陆劫持演示
- 分享一种最简单的Android打渠道包的方法
- android开发 自定义图文混排控件
- Android 动画详解
- android sdk 离线安装
- 获取Android手机SD卡容量大小
- Android笔记:去除标题栏
- Android清除本地数据缓存代码
- AndroidStudio开发工具快捷键整理分享
- Android事件分发机制
- 对于startActivity的使用改进
- android强行隐藏自带的输入法
- Mac下Android Studio快捷键
- 关于Android中的四大组件(Service的开启与关闭)
- android组件研究
- Android Camera 使用小结
- Android - sendOrderedBroadcast也可以这么用
- android 按需加载视图