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. supervisor-1:基础篇

    别人博客转载,做个记录 原文链接:http://lixcto.blog.51cto.com/4834175/1539136 有阵子没写博客了,这段时间一直在研究python django框架和前端相关 ...

  2. XE2 IntraWeb尝试

    新建--选择--确定 简单放几个控件在TIWForm1上面,运行,生成一个项目服务器:    复制 URL  http://127.0.0.1:8888/$/start 打开网页.

  3. iOS 键盘遮挡输入 解决办法

    .初始化及添加通知观察者 - (void)viewDidLoad { [super viewDidLoad]; self.tableView = [[UITableView alloc] initWi ...

  4. 关于DOM对象与JQuery对象的那些事

    这个问题源自上一次的工作室讨论班,主题是"jQuery选择器的使用",在讨论班的结尾,我留了一个思考题:  jQuery获取到的对象和直接调用原生Javascript方法获得的对象 ...

  5. [BZOJ1127][POI2008] KUP子矩阵

    Description 给一个n*n的地图,每个格子有一个价格,找一个矩形区域,使其价格总和位于[k,2k] Input 输入k n(n<2000)和一个n*n的地图 Output 输出矩形的左 ...

  6. HDU 1907 Nim博弈变形

    1.HDU 1907 2.题意:n堆糖,两人轮流,每次从任意一堆中至少取一个,最后取光者输. 3.总结:有点变形的Nim,还是不太明白,盗用一下学长的分析吧 传送门 分析:经典的Nim博弈的一点变形. ...

  7. id选择器、类选择器、属性选择器

    在网页编辑时,通常要对样式进行各种设置.我们借助CSS样式设计中的选择器,就能很好很方便的对它们进行管理和设置了. 今天,跟大家分享一下几个常用的选择器:id选择器.类选择器.属性选择器. id选择器 ...

  8. dede首页调用顶级栏目单页内容(如:关于我们)

    1.调用内容方法,调用描述同理: {dede:channel type="top" row=1} [field:array runphp='yes'] global $dsql; ...

  9. C#判断数组是否为空

    string[] array=new[] { "1", "2", "3", "4", "5" }; ...

  10. cf 红名计划!

    我要成为红名爷! 这是现在好弱好弱的窝 >_< ****************UPD ON 2015/12/10 0:20 啊啊啊啊啊啊啊啊啊啊啊啊把时间记错了啊QAQ 我也不知道为什么 ...