LruCache 源码解析
来源:互联网 发布:网络最好的诈骗文章 编辑:程序博客网 时间:2024/05/17 23:34
LruCache 作为AS的自带类,实现了一种内存缓存策略——最近使用算法,即在满足所拥有的内存限制的条件下,缓存近期使用的数据,而当数据大小超出限时时,移除最久未被使用的数据。LruCache常用如图片等的缓存。下面来看源码。
private final LinkedHashMap<K, V> map; private int size; // 当前缓存大小 private int maxSize; // 最大缓存大小 private int putCount; // 添加数据进缓存的次数 private int createCount; // 根据需要自己创建创建缓存数据的次数 private int evictionCount; // 移出缓存的次数 private int hitCount; // 命中,即找到对应数据的次数 private int missCount; // 找不到对应数据的次数
以上是LruCache的成员变量,可以看到LruCache是用LinkedHashMap实现的最近使用缓存策略,该数据结构继承HashMap,在此基础之上,它有一个双向链表 (如果忘了LinkedHashMap,先补补哦)。
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的构造方法,根据传入的最大内存限制,实例化LinkedHashMap
public void resize(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } synchronized (this) { this.maxSize = maxSize; } trimToSize(maxSize); } 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 || map.isEmpty()) { break; } Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } }
resize()方法能重新限定缓存大小,那这里会包含有一个问题,就是新的maxSize小于旧的maxSize,而此时的size大于新的maxSize , 为了避免这个问题的出现,在resize()方法里调用trimToSize()调整当前缓存。
trimToSize()函数有一个死循环,如果size小于maxSize,则此循环关闭。反之,移除map的一个最远key,使用函数safeSizeOf()更新size值和移除数量evictionCount值,重复此过程。
private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } return result; } protected int sizeOf(K key, V value) { return 1; }
上面提到的更新size值的方法safeSizeOf() ,该方法调用sizeOf(),sizeOf()是我们在实例化LruCache时,需要覆盖的函数, 返回值是所要缓存的数据的大小。
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()操作,put操作会将键值加入到map里,更新当前缓存size和加入次数的putCount值,返回原先的value值,即当前的key在此put之前已经对应了一个旧的value,当然也可能这个key还没有被使用。 所以size的更新可能需要更新两次。 紧接着还要调用trimToSize()调整内存的缓存数据,在加入新的value之后,可能超出了内存限制。
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
注意到在旧的value被移除后,调用了entryRemoved()方法。再默认情况下,次方法什么也不做,用来做为一个通知。也就是说,当新老数据交替时,我们可能会根据需要做一些事情,此时,就可以覆盖entryRemoved()方法。
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; } } protected V create(K key) { return null; }
get()的操作比较重要,也需要一定的理解,下面的说明会有点长
get()操作会从根据key从map里取出缓存的数据value,如果取到了,直接返回
当取不到的时候,会调用create()尝试创建一个value.。 那么在这里注意了,用的词是尝试。也就是说create()方法在默认情况什么也不做,返回null,而当get()里的create()返回null时候,也返回null。create()操作提供一种需要,就是在当前key还有被使用时,可能会需要根据这个key生成相应的value,此时,就需要覆盖create()完成此项工作,然后将value返回到get()。在这里,又衍生出了一个问题,即是LruCaChe是支持线程操作的,在create()的过程中(此过程还可能是耗时操作),map的结构可能已经改变,针对这一种情况,需要做相应的处理。
在①处将生成的createdValue加入到map中,mapValue 保存的是原来key对应的value。是的,没错,我们是因为根据key没有找到对应的value,才调用了create()生成了createdValue走到了这一步,怎么mapValue会有值呢?别忘了,LruCache是支持线程操作的,也就是其他的线程可能在create()的过程中,调用了put,利用了这个key,将value加入了map。 那么此时的key当然对应了一个值的。 当然,mapValue可能为null,因为在此之前key未被使用
在②处,mapValue保存了原来key对应的value,程序走到这里,说明别的线程使用了key,保存了对应的value。换而言之,不需要我们通过create()创建的createdValue了,但是key对应的value已经被我们更改为createdValue。那么,我们通过再次将mapValue加入到map中,就可以恢复原来的value值
在④处,因为不需要我们生成的createdValue,因此返回原来的mapValue,还使用了entryRemoved()进行了通知。
在③处,说明需要我们生成的createdValue,因此要更新当前内存size值
在⑤处,需要我们生成的createdValue,因此在加入这个createdValue后内存可能超限,因此要调整map
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; }
remove操作根据key删除相应的key和value,返回value
public synchronized final Map<K, V> snapshot() { return new LinkedHashMap<K, V>(map); }
返回map
到这里,解析完毕。LruCache常用操作为put和get,以及在实例化时需要覆盖的sizeOf。get方法是需要仔细体会的地方
此博客供个人笔记使用,如有不足,欢迎指出
- LruCache源码解析
- LRUCache源码解析
- LruCache源码解析
- LruCache源码完全解析
- 源码全面解析---LruCache
- LruCache源码解析
- LruCache 源码解析
- LruCache源码解析
- android LruCache源码解析
- LruCache源码解析
- LruCache源码解析
- LruCache源码完全解析
- LruCache 源码解析
- LruCache源码解析
- LruCache 源码解析
- android之LruCache源码解析
- 内存缓存LruCache源码解析
- 源码解析 ——LRUCache
- Android内存泄漏优化总结
- jdk9安装及java环境配置
- ubuntu服务器配置
- Android中使用Lambda
- 2017.10.3离线赛总结
- LruCache 源码解析
- 高级程序员之面试宝典
- C语言中 \r, \n, \b 的区别
- Implement strStr().
- C++类简单学习(六)
- oracle11g重建控制文件-关闭状态控制文件丢失--markdown格式
- qt调试时监视数组变量
- 关于dpkg一些小问题
- Bootstrap 栅格系统 学习总结 Bootstrap框架是如今最流行的前端框架之一,Bootstrap功能强大,简单易学,很符合实际应用场景。 只是Bootstrap的内容较多,新手