LRU的全称是Least Recently Used,表示最近最少使用,它的实现主要是靠内部的一个LinkedHashMap来实现的,它内部维护了一个LinkedHashMap对象集合,所有的数据都是保存在该集合中的。
LruCache是内存缓存,默认情况它缓存的数据都是强引用的,如果需要使用在本地磁盘缓存的话,可以使用DiskLruCache来实现。
下面通过阅读源码来理解LruCache的实现。
1、构造方法
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
//最近最少使用本身是LinkedHashMap中实现的,true表示的就是按照最近最少使用来排序插入,如果为false,那么它就是一个普通的map集合,按照顺序来插入的
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
我们在构造一个LruCache对象的时候,需要传递一个maxSize,该值表示当前能够在内存中缓存的最大数据,如果数据超过了该限制,那么就会调用trimToSize(maxSize)
方法来清除最近没有使用的对象在腾出一些内存空间出来。
2、清理方法trimToSize()
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
//如果当前缓存的数据大小小于0或者缓存的数据大小大于0但是map集合是空的话,就抛出异常,一般来说不会出现这种情况。
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中移除该对象
map.remove(key);
//重新计算size大小
size -= safeSizeOf(key, value);
//evictionCount表示被移除的对象数量,此处++
evictionCount++;
}
//最后调用entryRemoed()方法,默认是一个空实现。
entryRemoved(true, key, value, null);
}
}
trimToSize()方法用来清理当前最近没有使用的对象数据,如果当前已经缓存的数据超过了要求缓存的最大限制,那么就会循环的来清理对象直到当前缓存的数据大小小于最大限制。在清理完每一次最近没有使用的对象后就会调用entryRemoved(),它默认是一个空实现,在一些特殊情况我们可以实现此方法来做一些额外的事情,比如当我们缓存的bitmap对象被回收了,那么就可以在此方法中对bitmap调用recycle()方法来做一些额外的操作。
3、重新调整大小resize()
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
resize()方法可以对当前lruCache对象重新调整大小,当设置的大小比原来要小的话就会在trimToSize()中清理内存。
4、get()
public final V get(K key) {
//如果key值为空则抛出异常
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
//如果在map中找到就直接方法
if (mapValue != null) {
//hitCount表示命中数,即当前找到的总共次数
hitCount++;
return mapValue;
}
missCount++;
}
//如果map中没有的话就调用create方法来创建一个,默认create()返回null
V createdValue = create(key);
//如果没有创建就直接返回
if (createdValue == null) {
return null;
}
synchronized (this) {
//创建的总次数加1
createCount++;
//将创建的值放入到map集合中,返回原来的值
mapValue = map.put(key, createdValue);
if (mapValue != null) {
//如果集合中有值了那就将原来的值放回去,不用新创建的值
map.put(key, mapValue);
} else {
//如果原来就没有值的话那么重新计算当前缓存的总大小
size += safeSizeOf(key, createdValue);
}
}
//如果map中已经有值了那么久调用etryRemoved()方法,并将原来的值返回
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
//如果是新创建的值,那么需要触发调整内存空间并将其返回
trimToSize(maxSize);
return createdValue;
}
}
如果在map中没有获取到的话,会调用create()方法来创建key对应的value值,默认返回null,所以在使用的时候可以实现该方法来返回。有一点需要注意的是,在第一次从map中获取mapValue为null的情况下,在调用create()方法后又重新判断了mapValue是否为null的原因是,在多线程的情况下,第一次获取为null,在退出第一个synchronized后有可能别的线程就重新添加了该key对应的value进入,所以在第二个synchronized中需要重新判断mapValue是否为null来防止冲突。
5、put()
public final V put(K key, V value) {
//如果传入的key和value为null就抛出异常
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
//重新调整当前缓存的数据大小
size += safeSizeOf(key, value);
//将传入的值添加进map集合中,返回原来的值
previous = map.put(key, value);
if (previous != null) {
//如果原来有值就重新调整大小
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
//如果原来的值不为null,则调用entryRemoved()让子类来处理额外的操作
entryRemoved(false, key, previous, value);
}
//触发清理缓存
trimToSize(maxSize);
//将原来的值返回
return previous;
}
6、remove()
public final V remove(K key) {
//如果key值为null就抛出异常
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()让子类来处理
entryRemoved(false, key, previous, null);
}
return previous;
}
7、计算value大小sizeOf()
protected int sizeOf(K key, V value) {
return 1;
}
默认情况下sizeOf()返回的都是1,用于获取每一个value对象所占用的内存大小,所以我们在使用的时候一般都需要覆写该方法来返回value的真实的值,比如缓存的是bitmap,那么这里就需要返回一张bitmap占用的内存大小。
LRUCache的源码大致就这么多,其实并不复杂。