每天一篇系列:
强化知识体系,查漏补缺。
欢迎指正,共同学习!
软引用比较常见的使用是在图片缓存:
1.创建软引用HashMap作为缓存
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
2.向缓存中添加新Bitmap
public void addBitmapToCache(String path) {
// 强引用的Bitmap对象,这里bitmap是局部变量,该方法执行完毕后bitmap就会释放
Bitmap bitmap = BitmapFactory.decodeFile(path);
// 软引用的Bitmap对象
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
// 添加该对象到Map中使其缓存
imageCache.put(path, softBitmap);
}
3.从缓存中读取Bitmap
public Bitmap getBitmapByPath(String path) {
// 从缓存中取软引用的Bitmap对象
SoftReference<Bitmap> softBitmap = imageCache.get(path);
// 判断是否存在软引用
if (softBitmap == null) {
return null;
}
// 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空
Bitmap bitmap = softBitmap.get();
if(bitmap==null){
return null;
}
return bitmap;
}
在图片缓存的处理中除了软引用的优化外,主要涉及的是LRU算法策略,这里需要涉及到图片的三级缓存问题:
三级缓存的设计:
首先url作为缓存的关键字和bitmap对象关联起来,先从内存LruCache中查询是否存在缓存,如果没有再从DiskLruCache查询是否存在缓存,如果仍然没有缓存,则开启异步任务AsyncTask去网络下载。
LRU(Least Recently Used)算法的核心是当缓存满了的时候,回收近期使用最少的缓存对象。采用LRU算法的缓存有两种:LruCache和DiskLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。
1.LruCache的介绍
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;
...
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中。当缓存满时,把最近最少使用的对象从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。
LruCache的核心思想很好理解,就是要维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。这个缓存对象的列表就是LinkedHashMap,LinkedHashMap最近访问的最后输出的特性正好满足的LRU缓存算法的思想。
https://www.jianshu.com/p/b49a111147ee
可以看看LruCache的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;
}
其中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);
}
}
剩下的就是如何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) {
// 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;
}
}
get函数里面给开发者预留了一个可以继承的creat实现,当缓存不存在时,可以根据开发者自实现的缓存补充到LinkedHashMap中,并且对key值冲突的情况作了安全处理。
从LruCache的源码实现中希望可以深入到LinkedHashMap等集合的使用场合,synchronized同步的意义、泛型类的使用,父类调用sizeof是否使用了某种设计模式等更广层面的思考。
2.DiskLruCache介绍
public final class DiskLruCache implements Closeable {
...
private final LinkedHashMap<String, Entry> lruEntries
= new LinkedHashMap<String, Entry>(0, 0.75f, true);
...
private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
...
}
不同于LruCache,LruCache是将数据缓存到内存中去,而DiskLruCache是外部缓存,例如可以将网络下载的图片永久的缓存到手机外部存储中去。同样DiskLruCache内部也利用了LinkedHashMap特性,还利用了ExecutorService来优化线程池。
DiskLruCache被声明未final,表明这个类不能被继承。
(当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法)
(使用final修饰方法的原因是把方法锁定,以防任何继承类修改它的含义)
(对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象)。
其中LinkedHashMap的使用与LRU方式没有差别。通过一个文件记录缓存读取记录,从而记录缓存事件。
public synchronized Snapshot get(String key) throws IOException {
checkNotClosed();
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) {
return null;
}
if (!entry.readable) {
return null;
}
/*
* Open all streams eagerly to guarantee that we see a single published
* snapshot. If we opened streams lazily then the streams could come
* from different edits.
*/
InputStream[] ins = new InputStream[valueCount];
try {
for (int i = 0; i < valueCount; i++) {
ins[i] = new FileInputStream(entry.getCleanFile(i));
}
} catch (FileNotFoundException e) {
// a file must have been deleted manually!
return null;
}
redundantOpCount++;
journalWriter.append(READ + ' ' + key + '\n');
if (journalRebuildRequired()) {
executorService.submit(cleanupCallable);
}
return new Snapshot(key, entry.sequenceNumber, ins);
}
网友评论