转载请附原文链接: LruCache 源码学习笔记
上一篇笔记分析了 Volley 中的 ImageLoder ,作者推荐使用 LruCache 来作为一级缓存。本篇笔记就来分析一下 LruCache 是如何实现缓存的。如果你对 LruCache 还不了解,建议先学会使用,本篇笔记直接上源码,不会介绍使用。
LruCache 是 Android 中的一个缓存工具类,在 Android 3.1 的时候加入 util 包中,对于低版本的上可以在 support 包中找到这个类。该类以 key-value 的形式保存数据,不允许 null key 或者是 null value ,因此只要get(key) 只要返回空,说明缓存中没有这个 key 对应的数据。
LruCache 实际上是 LinkedHashMap + 最近使用算法,下面就进入源码中去
初始化
构造方法指定最大容量,初始化一个 LinedHashMap
private int size; private int maxSize; private int putCount; private int createCount; private int evictionCount; private int hitCount; private int missCount; 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 方法来确定每一个元素的大小,默认返回是1
protected int sizeOf(K key, V value) { return 1; }
|
trimToSize () 核心代码
LruCache 中有一个方法 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; } 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); } }
|
上面就是调整缓存空间的核心代码,总共没有几行。剩下的就是简单的查找,增加,移除等等操作
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 += 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; }
|
在 LruCache 中只要有元素被移除就会调用一个方法 entryRemoved(),复写这个方法可以自定义你自己的冲突处理策略。
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
|
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) { 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 在获取缓存失败了之后,还提供了创建的方法。
protected V create(K key) { return null; }
|
remove()
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; }
|
结语
LruCache 的源码就分析完了,发现其实原理没有想象的复杂,事实上有很多牛逼的框架原理也没有想象的那么难,可能就是用最简单的逻辑堆叠而成。