分析的LurCache来自于开源库picasso。
一、LruCache初始化
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("Max size must be positive.");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<>(0, 0.75f, true);
}
LruCache初始化很简单,只用传一个maxSize就行了。内部用LinkedHashMap存储值。前两个值是和LinkedHashMap原理有关,一个是LinkedHashMap的Capacity,0.75是负载因子。
//当出现下面情况时,HashMap就会扩容
HashMap.Size >= Capacity * LoadFactor
注意最后一个值传的true。看看LinkedHashMap构造函数
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
accessOrder的作用是排序,因为LRU算法是按查找顺序排序,这个参数正好排上用场。比如原始Map中值的顺序是ABCD,这时查找AB,那么LinkedHashMap重新排序顺序为CDAB,最后查找的排在list尾部,这样等容量超过LruCache初始化的最大值maxSize时,就可以从list头开始删除。
二、LurCache查找
查找调用get()方法
@Override public Bitmap get(@NonNull String key) {
if (key == null) {
throw new NullPointerException("key == null");
}
Bitmap mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
return null;
}
查找过程简单,命中就hitCount+1,否则missCount+1,有些版本的LruCache还会在没命中时建个新的value插入,这个版本没有,更加简洁。
三、LurCache插入
插入调用set()方法
@Override public void set(@NonNull String key, @NonNull Bitmap bitmap) {
if (key == null || bitmap == null) {
throw new NullPointerException("key == null || bitmap == null");
}
int addedSize = Utils.getBitmapBytes(bitmap);
if (addedSize > maxSize) {
return;
}
synchronized (this) {
putCount++;
size += addedSize;
Bitmap previous = map.put(key, bitmap);
if (previous != null) {
size -= Utils.getBitmapBytes(previous);
}
}
trimToSize(maxSize);
}
流程是先取得将要插入图片的大小,如果图片比maxSize都大,就直接返回,没有插入到LinkedHashMap保存(这样搞大图片岂不是删不了?)。接着就算出当前size,并插入到LinkedHashMap,这里注意,如果LinkedHashMap之前有一样的key,那之前一样key的value会被这次的value覆盖掉,所以出现这种情况,就要用当前size 减掉被覆盖的图片大小,得到当前最新的size。这里用同步块,是因为LinkedHashMap不是线程安全的,如果几个线程同时操作,就乱套了。
好了,现在得到当前最新的size了,如果size比maxSize大,就要将最不常用的删除,如果没有就结束。最后一步调用trimToSize就是干这事的,遍历LinkedHashMap,从头开始,一个个判断,每删一个,就和maxSize比较下,直到当前的size小于maxSize,就跳出循环,结束。
private void trimToSize(int maxSize) {
while (true) {
String key;
Bitmap 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<String, Bitmap> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= Utils.getBitmapBytes(value);
evictionCount++;
}
}
}
上面提到如果插入的图片比maxSize都大,就直接返回,这样显然不好,于是找了下Glide的LruCache源码看,看看Glide怎么处理这种情况,Glide通过一个回调函数onItemEvicted处理,想怎么处理,用户自己决定。
public Y put(T key, Y item) {
final int itemSize = getSize(item);
if (itemSize >= maxSize) {
onItemEvicted(key, item);//用户自己可以覆盖,原函数是空的
return null;
}
//以下流程都差不多......
}
/**
* A callback called whenever an item is evicted from the cache. Subclasses can override.
*
* @param key The key of the evicted item.
* @param item The evicted item.
*/
protected void onItemEvicted(T key, Y item) {
// optional override
}
最后提下怎么计算图片的大小,算法是这样:
static int getBitmapBytes(Bitmap bitmap) {
int result = SDK_INT >= KITKAT ? bitmap.getAllocationByteCount() : bitmap.getByteCount();
if (result < 0) {
throw new IllegalStateException("Negative size: " + bitmap);
}
return result;
}
在sdk19之后的调用getAllocationByteCount,之前的调用getByteCount。
网友评论