LruCache的Lru指的是LeastRecentlyUsed,也就是近期最少使用算法。也就是说,当我们进行缓存的时候,如果缓存满了,会先淘汰使用的最少的缓存对象。

为什么要用LruCache?其实使用它的原因有很多,例如我们要做一个电子商务App,如果我们不加节制的向服务器请求大量图片,那么对于服务器来说是一个不少的负担,其次,对于用户来说,每次刷新都意味着流量的大量消耗以及长时间等待,所以缓存机制几乎是每个需要联网的App必须做的。

LruCache已经存在于官方的API中,所以无需添加任何依赖即可使用,而这个缓存只是一个内存缓存,并不能进行本地缓存,也就是说,如果内存不足,缓存有可能会失效,而且当App重启的时候,缓存会重新开始生效。如果想要进行本地磁盘缓存,推荐使用DiskLruCache,虽然没包含在官方API中,但是官方推荐我们使用,本文暂不讨论。

使用方法:

使用LruCache其实非常简单,下面以一个图片缓存为例:

创建LruCache对象:

private static class StringBitmapLruCache extends LruCache<String, Bitmap> {
public StringBitmapLruCache() {
// 构造方法传入当前应用可用最大内存的八分之一
super((int) (Runtime.getRuntime().maxMemory() / 1024 / 8));
} @Override
// 重写sizeOf方法,并计算返回每个Bitmap对象占用的内存
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount() / 1024;
} @Override
// 当缓存被移除时调用,第一个参数是表明缓存移除的原因,true表示被LruCache移除,false表示被主动remove移除,可不重写
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap
newValue) {
super.entryRemoved(evicted, key, oldValue, newValue);
} @Override
// 当get方法获取不到缓存的时候调用,如果需要创建自定义默认缓存,可以在这里添加逻辑,可不重写
protected Bitmap create(String key) {
return super.create(key);
}
}
LruCache<String, Bitmap> mLruCache = new StringBitmapLruCache();

把图片写入缓存:

mLruCache.put(name, bitmap);

从缓存读取图片:

mLruCache.get(name);

从缓存中删除图片:

mLruCache.remove(name);

使用的方法很简单,一般我们直接通过get方法读取缓存,如果返回Null,再通过网络访问图片,访问之后,再把图片put到缓存中,这样下次访问就可以获取到。

至此,我们已经基本了解了LruCache的用法,我们不需要进行任何的淘汰处理,LruCache会自动帮我们完成淘汰的工作。

源码分析:

构造方法:

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);
}

可以看到,构造方法中我们获取了缓存的最大值,并且创建了一个LinkedHashMap对象,这个对象就是整个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;
}

解析:put方法中,先计算插入的对象类型的大小,调用的方法是safeSizeOf,这个方法其实只是简单的调用了我们在构造的时候重写的sizeOf方法,如果返回负数,则抛出异常。接着把我们需要缓存的对象插入LinkedHashMap中,如果缓存中有这个对象,就把size复位。如果缓存中有这个key对应的对象,则调用entryRemoved方法,这个方法默认为空,但是如果我们需要在缓存更新之后进行一些记录的话,可以通过在构造时重写这个方法来做到。接下来,调用trimToSize方法,这个方法是去检查当前的size有没有超过maxSize,这里我们看看源码

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 || map.isEmpty()) {
break;
} Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
} entryRemoved(true, key, value, null);
}
}

可以看到,这里的判断逻辑也很简单,通过不断的检查,如果超过maxSize,则从LinkedHashMap中剔除一个,直到size等于或者小于maxSize,这里同样会调用entryRemoved方法。

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方法的时候,直接从LinkedHashMap中get一个当前key的对象并返回,如果返回的为Null,则会调用create方法来创建一个对象,而create方法默认也是一个空方法,直接返回null,所以,如果你需要在get失败的时候创建一个默认的对象,可以在构造的时候重写create方法。如果重写了create方法,那么下面的代码会被执行,先进行LinkedHashMap的插入方法,如果已经存在,则返回存在的对象,否则返回我们创建的对象。这里可以看到,这里重复判断列表中是否已经存在相同的对象,原因是,如果create方法处理的时间过长,有可能create出来的对象已经被put到LinkedHashMap中了。

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;
}

解析:这里逻辑也很清晰,跟上面的两个方法也很类似,就不唠嗑了。

其他的一些地方,看看源码就行,睡了,晚安。

最新文章

  1. ANNOTATION PROCESSING 101 by Hannes Dorfmann — 10 Jan 2015
  2. FileIputeStream用于读写文件,并且用字节的方式表示出来
  3. time()函数,dirname(__FILE__) 的使用总结
  4. iOS开发拓展篇—音频处理(音乐播放器6)
  5. Sqlite 学习记录
  6. 对象的引用和clone
  7. Entity Framework 并发处理借鉴
  8. SkyDrive Pro client now available as standalone download. Hurray!
  9. 数据挖掘十大经典算法(5) 最大期望(EM)算法
  10. 2017.3.12 H5学习的第一周
  11. 43. leetcode 459. Repeated Substring Pattern
  12. VisualVM 使用 service:jmx:rmi:///...无法连接linux远程服务器
  13. opencv-python下简单KNN分类识别
  14. Java核心技术卷一 &#183; 笔记(1)
  15. 为什么js中要用void 0 代替undefined
  16. SNF快速开发平台成长史V4.5-Spring.Net.Framework-SNF软件开发机器人
  17. C#二分查找算法设计实现
  18. ubuntu下设置jupyter notebook 2017年07月29日 19:28:34 小旋锋 阅读数:8329 标签: ubuntu 更多 个人分类: python 二三事 来源:http://blog.csdn.net/suzyu12345/article/details/51037905 Ipython Notebook现在已经改名为Ipython jupyter,是最知名最好用的
  19. Docker 仓库管理
  20. 怎样在Windows与Centos下的Linux间共享文件,如果mnt文件夹不显示,可能是mnt缺少共享支持

热门文章

  1. JAVA 引入 junit工具框架
  2. BZOJ 2683 简单题 ——CDQ分治
  3. iOS10以上关于访问权限设置
  4. 【转】Oracle执行计划解释
  5. js数组与对象性能比较
  6. .net开源后可以查看的源代码
  7. sql with as union all
  8. 使用SQL Server 2008 维护计划(图解)
  9. 【noip 2016】 蚯蚓(earthworm)
  10. 验证mongodb主从复制过程~记录操作