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

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

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

Android开发学习之路-LruCache使用和源码分析的更多相关文章

  1. Android开发学习之路-RecyclerView滑动删除和拖动排序

    Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开 ...

  2. Android开发学习之路--基于vitamio的视频播放器(二)

      终于把该忙的事情都忙得差不多了,接下来又可以开始good good study,day day up了.在Android开发学习之路–基于vitamio的视频播放器(一)中,主要讲了播放器的界面的 ...

  3. Android开发学习之路--Android Studio cmake编译ffmpeg

      最新的android studio2.2引入了cmake可以很好地实现ndk的编写.这里使用最新的方式,对于以前的android下的ndk编译什么的可以参考之前的文章:Android开发学习之路– ...

  4. Android开发学习之路--网络编程之xml、json

    一般网络数据通过http来get,post,那么其中的数据不可能杂乱无章,比如我要post一段数据,肯定是要有一定的格式,协议的.常用的就是xml和json了.在此先要搭建个简单的服务器吧,首先呢下载 ...

  5. Android开发学习之路--Activity之初体验

    环境也搭建好了,android系统也基本了解了,那么接下来就可以开始学习android开发了,相信这么学下去肯定可以把android开发学习好的,再加上时而再温故下linux下的知识,看看androi ...

  6. Android开发学习之路--Android系统架构初探

    环境搭建好了,最简单的app也运行过了,那么app到底是怎么运行在手机上的,手机又到底怎么能运行这些应用,一堆的电子元器件最后可以运行这么美妙的界面,在此还是需要好好研究研究.这里从芯片及硬件模块-& ...

  7. Android开发学习之路--MAC下Android Studio开发环境搭建

    自从毕业开始到现在还没有系统地学习android应用的开发,之前一直都是做些底层的驱动,以及linux上的c开发.虽然写过几个简单的app,也对android4.0.3的源代码做过部分的分析,也算入门 ...

  8. Android开发学习之路-记一次CSDN公开课

    今天的CSDN公开课Android事件处理重难点快速掌握中老师讲到一个概念我觉得不正确. 原话是这样的:点击事件可以通过事件监听和回调两种方法实现. 我一听到之后我的表情是这样的: 这跟我学的看的都不 ...

  9. Android开发学习之路-Android Studio开发小技巧

    上一次发过了一个介绍Studio的,这里再发一个补充下. 我们都知道,Android Studio的功能是非常强大的,也是很智能的.如果有人告诉你学Android开发要用命令行,你可以告诉他Andro ...

随机推荐

  1. mysql查询练习题-2016.12.16

    >>>>>>>>>> 练习时间:2016.12.16 编辑时间:2016-12-20-->22:12:08 题: 涉及:多表查询.ex ...

  2. 25个 Git 进阶技巧

    [ 原文] http://www.open-open.com/lib/view/open1431331496857.html 我已经使用git差不多18个月了,觉得自己对它应该已经非常了解.然后来自G ...

  3. windows系统时间(SYSTEMTIME)

    GetSystemTime函数获得当前的UTC时间,GetLocalTime获得当前的本地时间 UTC是协调世界时(Universal Time Coordinated)英文缩写,是由国际无线电咨询委 ...

  4. JFreeChart

    花了四个小时给同学写的.还行吧,原来都没有用过到处找资料写的. package DrawLine; import org.jfree.chart.ChartFactory; import org.jf ...

  5. Leetcode Maximum Product Subarray

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  6. JS 中 Array.slice() 和 Array.splice()方法

    slice slice()就是对应String的substring()版本,它截取Array的部分元素,然后返回一个新的Array: var arr = ['A', 'B', 'C', 'D', 'E ...

  7. [ACM训练] 算法初级 之 基本算法 之 枚举(POJ 1753+2965)

    先列出题目: 1.POJ 1753 POJ 1753  Flip Game:http://poj.org/problem?id=1753 Sample Input bwwb bbwb bwwb bww ...

  8. 解决python编码格式错误问题

    一:前言 遇到问题:print输入汉字时提示错误信息 UnicodeDecodeError: 'ascii' codec can't decode byte 0x?? in position 1: o ...

  9. mas_makeConstraints && mas_remakeConstraints && mas_updateConstraints 用法与注意事项

    mas_makeConstraints && mas_remakeConstraints && mas_updateConstraints 用法与注意事项 字数400 ...

  10. 印象笔记 剪藏(Evernote WebClipper) bug 记录

    问题记录: Chrome版的 webclipper不知为何新装的时候切换到中国版印象笔记登陆的时候闪退,然后之后就无法进入中国区的登录页面:international版确认可以登录. cookies是 ...