以下针对 Android API 26 版本的源码进行分析。

在了解LruCache之前,最好对LinkedHashMap有初步的了解,LruCache的实现主要借助LinkedHashMapLinkedHashMap的源码解析,可阅读Java——LinkedHashMap源码解析

概述

  LruCahce其 Lru 是 Least Recently Used 的缩写,即最近最少使用,是包含对有限数量值的强引用的缓存。每当一个值被访问,它将被移到队尾。当缓存达到指定的数量时,位于队头的值将被移除,并且可能被 GC 回收。如果缓存的值包含需要显式释放的资源,那么需要重写entryRemoved方法。如果 key 对应的缓存未命中,通过重写create方法创建对应的 value。这可以简化代码调用:即使存在缓存未命中,也允许假设始终返回一个值。

  默认情况下,缓存大小以条目数量度量。在不同缓存对象下,通过重写sizeOf方法测量 key-value 缓存的大小。例如如下的例子,这个缓存限制了 4MiB 大小的位图:

int cacheSize = 4 * 1024 * 1024; // 4MiB
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
}

  这个类是线程安全的,通过在缓存上执行同步操作来以原子方式执行多个缓存操作:

synchronized (cache) {
if (cache.get(key) == null) {
cache.put(key, value);
}
}

  这个类不允许空值作为 key 或者 value,对于getputremove方法返回null值是明确的行为:缓存中不存在这个键。

源码分析

主要字段

    //LruCache 主要借助 LinkedHashMap 按元素访问顺序的迭代顺序(此时 accessOrder = true)来实现
private final LinkedHashMap<K, V> map; /** 不同 key-value 条目下缓存的大小,不一定是 key-value 条目的数量 */
private int size;
//缓存大小的最大值
private int maxSize; //存储的 key-value 条目的个数
private int putCount;
//创建 key 对应的 value 的次数
private int createCount;
//缓存移除的次数
private int evictionCount;
//缓存命中的次数
private int hitCount;
//缓存未命中的次数
private int missCount;

构造函数

/**
* maxSize 对于缓存没有重写 sizeOf 方法的时候,这个数值指定了缓存中可以容纳的最大条目的数量;
* 对于其他缓存,这是缓存中条目大小的最大总和。
*/
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize; //指定了哈希表初始容量为0,负载因子为0.75,迭代顺序为按照条目访问顺序
//因此在有对条目进行访问的操作的时候,条目都会被放置到队尾,具体细节详看 LinkedHashMap 的解析
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}

Size操作

LruCache在默认情况下,size 指的是 key-value 条目的个数,当重写sizeOf函数时,可以自定义 key-value 条目的单位大小,如概述中位图的例子,其通过重写sizeOf函数,返回的大小值并非是 1,而是不同Bitmap对象的字节大小。

/**
* 以用户定义的单位返回 key-value 条目的大小
* 默认实现返回1,因此 size 是条目数,max size是最大条目数
* 条目的大小在缓存中时不得更改
*/
protected int sizeOf(K key, V value) {
return 1;
} private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
} /**
* 删除最旧的条目,直到剩余条目总数小于等于指定的大小。
*/
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++;
} //此处 evicted 为 true,表明是为了腾出空间而进行的删除条目操作
entryRemoved(true, key, value, null);
}
} /**
* 调整缓存的大小
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
} synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}

查询

/**
* 指定 key 对应的 value 值存在时返回,否则通过 create 方法创建相应的 key-value 对。
* 如果对应的 value 值被返回,那么这个 key-value 对将被移到队尾。
* 当返回 null 时,表明没有对应的 value 值并且也无法被创建
*/
public final V get(K key) {
//缓存不允许 key 值为 null,因此对于查询 null 的键可直接抛出异常
if (key == null) {
throw new NullPointerException("key == null");
} V mapValue;
synchronized (this) {
mapValue = map.get(key);
//缓存命中
if (mapValue != null) {
hitCount++;
return mapValue;
}
//缓存未命中
missCount++;
} /*
* 尝试创建一个 value 值,这可能需要花费较长的时间完成,当 create 返回时,哈希表可能变得不同
* 如果在 create 工作时向哈希表添加了一个冲突的值(key 已经有对应的 value 值,但 create 方法返回了一个不同的 value 值)
* 那么将该值保留在哈希表中并释放创建的值。
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
} synchronized (this) {
//缓存创建的次数
createCount++;
mapValue = map.put(key, createdValue); if (mapValue != null) {
// mapValue 不为 null,说明存在一个冲突值,保留之前的 value 值
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
} if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
} /**
* 在缓存未命中之后调用以计算相应 key 的 value。
* 当能计算 key 对应的 value 时,返回 value,否则返回 null。默认实现一律返回 null 值。
*
* 这个方法在被调用的时候没有添加额外的同步操作,因此其他线程可能在这个方法执行时访问缓存
*
* 如果 key 对应的 value 存储在缓存中,那么通过 create 创建的 value 将通过 entryRemoved 方法释放。
* 这种情况主要发生在:当多个线程同时请求相同的 key (导致创建多个值)时,或者当一个线程调用 put 而另一个线程为其创建值时
*/
protected V create(K key) {
return null;
}

存储

/**
* 对于 key,缓存其相应的 value,key-value 条目放置于队尾
*
* @return 返回先前 key 对应的 value 值
*/
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) {
//evicted 为 true,表明不是为了腾出空间而进行的删除操作
entryRemoved(false, key, previous, value);
} trimToSize(maxSize);
return previous;
}

删除

/**
* 删除 key 对应的条目
*
* 返回 key 对应的 value值
*/
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;
} /**
* 当条目需要被移除或删除时调用
* 当一个值被移除以腾出空间,通过调用 remove 删除,或者被 put 调用替换值时,会调用此方法。默认实现什么也不做
*
* 这个方法在被调用的时候没有添加额外的同步操作,因此其他线程可能在这个方法执行时访问缓存
*
* @param evicted true 表明条目正在被删除以腾出空间,false 表明删除是由 put 或 remove 引起的(并非是为了腾出空间)
*
* @param newValue key 的新值。如果非 null,则此删除是由 put 引起的。否则它是由 remove引起的
*/
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

Android——LruCache源码解析的更多相关文章

  1. Android -- AsyncTask源码解析

    1,前段时间换工作的时候,关于AsyncTask源码这个点基本上大一点的公司都会问,所以今天就和大家一起来总结总结.本来早就想写这篇文章的,当时写<Android -- 从源码解析Handle+ ...

  2. 还怕问源码?Github上神级Android三方源码解析手册,已有7.6 KStar

    或许对于许多Android开发者来说,所谓的Android工程师的工作"不过就是用XML实现设计师的美术图,用JSON解析服务器的数据,再把数据显示到界面上"就好了,源码什么的,看 ...

  3. Android AsyncTask 源码解析

    1. 官方介绍 public abstract class AsyncTask extends Object  java.lang.Object    ↳ android.os.AsyncTask&l ...

  4. Android EventBus源码解析 带你深入理解EventBus

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...

  5. Android -- 从源码解析Handle+Looper+MessageQueue机制

    1,今天和大家一起从底层看看Handle的工作机制是什么样的,那么在引入之前我们先来了解Handle是用来干什么的 handler通俗一点讲就是用来在各个线程之间发送数据的处理对象.在任何线程中,只要 ...

  6. Android LayoutInflater源码解析:你真的能正确使用吗?

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 好久没写博客了,最近忙着换工作,没时间写,工作刚定下来.稍后有时间会写一下换工作经历.接下来进入本篇主题,本来没想写LayoutInflater的 ...

  7. Android HandlerThread源码解析

    在上一章Handler源码解析文章中,我们知道App的主线程通过Handler机制完成了一个线程的消息循环.那么我们自己也可以新建一个线程,在线程里面创建一个Looper,完成消息循环,可以做一些定时 ...

  8. 【转载】LruCache 源码解析

    原文地址:https://github.com/LittleFriendsGroup/AndroidSdkSourceAnalysis/blob/master/article/LruCache%E6% ...

  9. Android DiskLruCache 源码解析 硬盘缓存的绝佳方案

    一.概述 依旧是整理东西,所以近期的博客涉及的东西可能会比较老一点,会分析一些经典的框架,我觉得可能也是每个优秀的开发者必须掌握的东西:那么对于Disk Cache,DiskLruCache可以算佼佼 ...

随机推荐

  1. Python socket服务

    套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开.读写和关闭等操作. 1. 实现客户端发送字符,服务器返回大写的字符: 服务器: import soc ...

  2. idea+maven3.6.1构建maven工程报PKIX:unable to find valid certification path to requested target

    转载于  https://www.cnblogs.com/xiaoping1993/p/9717649.html 注意可能在idea工具执行java命令提示找不到类,返回类的最上层包路径 然后再执行j ...

  3. @Autowired与@Resource的区别(转载)

    原文地址:http://bhdweb.iteye.com/blog/1663907 1.@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上.  ...

  4. ajax 请求成功,但是后台feigin请求超时解决方案

    ========后台请求数据时间较长,报feigin超时错误====== fegin报错如下: feign.RetryableException: Read timed out executing P ...

  5. Android 在 4G 下访问 IPV6 慢的解决方案

    Android 在 4G 下访问 IPV6 慢的解决方案 Android4G ipv6 起因 今天,用户反馈 Android 端加载数据较慢,经 Android 开发人员排查后,发现在公司 wifi ...

  6. Django对中间件的调用思想、csrf中间件详细介绍、Django settings源码剖析、Django的Auth模块

    目录 使用Django对中间件的调用思想完成自己的功能 功能要求 importlib模块介绍 功能的实现 csrf中间件详细介绍 跨站请求伪造 Django csrf中间件 form表单 ajax c ...

  7. py从入门到实践 第四章

    4.1 遍立列表 ~= shell 数组————————————————————————————————————————————thrink = ['link','path','pwd']for i ...

  8. 开发一个chrome插件:将百度搜索热点屏蔽掉!

    每次百度搜索,搜索结果的右边总是出现些乱七八糟的搜索热点(推的都是些什么玩意,高校替课和我有毛关系,几个悲伤的热点我用星号顶掉了). 强迫症想把它隐藏掉,我用的是chrome浏览器,受adblock( ...

  9. mini-batch

    我们在训练神经网络模型时,最常用的就是梯度下降,梯度下降有一下几种方式: 1.Batch gradient descent(BGD批梯度下降) 遍历全部数据集算一次损失函数,然后算函数对各个参数的梯度 ...

  10. idea启动或install时报错:There are test failures,如何跳过测试?

    用idea  install项目时失败,报这样的错: [INFO] BUILD FAILURE [INFO] --------------------------------------------- ...