序言
在Android中缓存策略有着广泛的应用场景,尤其是在图片加载从应用场景下,基本上都要用到缓存。因为图片加载需要消耗很大量的流量,在移动应用开发中不能过多的消耗用户的流量,一是因为流量是收费的,第二是过大的请求量会造成图片加载很慢用户体验不好。因此在图片加载过成功就要使用到缓存。
那么什么是缓存策略呢?缓存策略主要包含缓存的添加,读取和删除这三个操作。添加和读取没有什么好说的,缓存策略主要是删除这块,因为移动设备存储空间以及内存都是有限的,因此在使用缓存的时候要指定最大的缓存空间,当分配的缓存空间占用完之后如果还要缓存新的东西就要删除一些就的缓存,怎么样去定义缓存新旧这就是一种策略,不同的策略就要用到不同的缓存算法。
目前常用的一种缓存算法是Least Recently Used,简称:LRU,LRU是近期最少使用算法,它的核心机制是当缓存控件满时,会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有:LruCache以及DiskLruCache,LruCache用于实现内存缓存,DiskLruCache用于实现存储设备缓存,因此通过这二者的结合使用,就可以很方便地实现一个高效的ImageLoader。
LUR实现实现原理:
1.LruCache的介绍
LruCache是Android 3.1所提供的一个缓存类,所以在Android中可以直接使用LruCache实现内存缓存。LruCache是个泛型类,主要算法原理是把最近使用的对象用强引用(即我们平常使用的对象引用方式)存储在 LinkedHashMap 中。当缓存满时,把最近最少使用的对象从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。
2.LruCache的使用
我们就以图片缓存为例
int maxMemory = (int) (Runtime.getRuntime().totalMemory() / 1024);
//缓存大小一般为 当前进程的1/8
int cacheSize = maxMemory / 8;
//缓存对象列表 其中由LinkedHashMap 来维护的
//LinkedHashMap 是由数组+双向链表的数据结构来实现的
LruCache lruCache = new LruCache<String,Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
上片段代码中只要展示了 LruCache 使用
1.首先设置LruCache的缓存大小
2.重写sizeOf方法 来确定BitMap的缓存大小
3、LruCache的实现原理
LruCache内部用一个LinkedHashMap以强引用的方式存储外界的缓存对象,提供了get和set对象来完成缓存的获取和添加操作,当缓存满时,会移除较早使用的缓存对象,然后再添加新的缓存对象。
LinkedHashMap主要由数组+双向链表的数据结构来实现的其中双向链表的结构可以实现访问顺序和插入顺序,使得LinkedHashMap中的对按照一定顺序排列起来。
/**
* Constructs an empty insertion-ordered LinkedHashMap instance
* with the specified initial capacity and a default load factor (0.75).
*
* @param initialCapacity the initial capacity
* @throws IllegalArgumentException if the initial capacity is negative
*/
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
上片段代码主要是LinkedHashMap初始方法 根据官方注释来看
参数 initialCapacity 为初始容量 也就是相对于LruCache 初始化中缓存的大小
accessOrder 为访问顺序 默认为 false,插入顺序 true为访问顺序
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
上片段代码主要解析了 LinkedHashMap 在取值时候的过程,显示判断时候存在,再根据初始化的时候
accessOrder 参数来判断进行一个插入还是 访问的顺序,当前我们来看accessOrder 参数为 true 访问时候的
情况
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
上片段代码主要业务逻辑为 将当前的拿到的 值 移动到最后 move node to last
首先if 判断 当前为访问 非 插入 再次判断当前的节点 node 的值是否等于 LinkedHashMap 最后一个节点的值
如果不同 则进行操作主要还是进行一个 交换 将当前的node节点移动到最后。
下面我们来看看LruCache源码
public LruCache(final int capacity) {
mLruMap = new LinkedHashMap<K, V>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > capacity;
}
};
}
从上片段代码中我们看到 在LruCache 初始化中,同样初始化了 LinkendHashMap,从而得之,LruCache内部
实现是由LinkedHashMap实现的 并且默认为 访问顺序
我们再来看看Lru的put get方法
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created.
*/
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
//获取当前的需要的值
synchronized (this) {
//从LinkedList中获取值返回
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;
}
}
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
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++; //插入缓存对象+1
size += safeSizeOf(key, value);//增加现有的缓存大小
previous = map.put(key, value);//向LinkedHashMap 插入缓存对象
if (previous != null) { //如果 LinkedHashMap 已有缓存对象 则恢复原有的小
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
//调整缓存大小
trimToSize(maxSize);
return previous;
}
put方法
put()方法并没有什么难点,重要的就是在添加过缓存对象后,调用 trimToSize()方法,来判断缓存是否已满,如果满了就要删除近期最少使用的算法。
/**
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
*/
private void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
//如果map为空并且缓存size不等于0或者缓存size小于0,抛出异常
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
// BEGIN LAYOUTLIB CHANGE
// get the last item in the linked list.
// This is not efficient, the goal here is to minimize the changes
// compared to the platform version.
//获取队尾的对象 近期访问最少的元素 删除
Map.Entry<K, V> toEvict = null;
for (Map.Entry<K, V> entry : map.entrySet()) {
toEvict = entry;
}
// END LAYOUTLIB CHANGE
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
//删除
map.remove(key);
//恢复大小
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}