LruCache解析
时间:2022-06-13
本文章向大家介绍LruCache解析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
概述
LRU
(Least Recently Used),即最近最少使用算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。 该算法被应用在LruCache
和DiskLruCache
,分别用于实现内存缓存和磁盘缓存。
LruCache的介绍
LruCache是个泛型类,主要算法原理是把最近使用的对象用强引用存储在
LinkedHashMap
中,当缓存满时,把最近最少使用的对象从内存中移除,并提供了get和put方法来完成缓存的获取和添加操作。
LruCache的使用
// 设置LruCache缓存的大小,一般为当前进程可用容量的1/8
int cacheSize = (int) (Runtime.getRuntime().totalMemory() / 8);
LruCache<String, Bitmap> mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
// 重写sizeOf方法,计算出要缓存的每张图片的大小
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
NOTE:缓存的总容量和每个缓存对象的大小所用的单位要一致。
LruCache的实现原理
LruCache的核心思想:维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没有访问的对象,将放在队头,最早被淘汰,而最近访问的对象将放在队尾,最晚被淘汰。
LruCache的实现是使用LinkedHashMap来维护这个对象队列的。
/**
* @param initialCapacity 初始化大小
* @param loadFactor 加载因子,用于当容量不足,自动扩大
* @param accessOrder true访问顺序,false为插入顺序
*/
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
LinkedHashMap使用示例:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinkedHashMap<String, String> map = new LinkedHashMap<>(0, 0.75f, true);
map.put("A", "A");
map.put("B", "B");
map.put("C", "C");
map.put("D", "D");
map.put("E", "E");
map.put("F", "F");
map.get("A");
map.get("B");
for (Map.Entry<String, String> entry : map.entrySet()) {
Log.i("TAG", "key:" + entry.getKey() + " value:" + entry.getValue());
}
}
}
运行结果:
I/TAG: key:C value:C
I/TAG: key:D value:D
I/TAG: key:E value:E
I/TAG: key:F value:F
I/TAG: key:A value:A
I/TAG: key:B value:B
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的访问顺序。
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) {
// 插入的缓存对象数加1
putCount++;
// 增加缓存对象的大小
size += safeSizeOf(key, value);
// 向map中加入缓存对象
previous = map.put(key, value);
// 如果有缓存对象,则缓存大小恢复到插入前
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
// entryRemoved是空方法,可以自行实现
if (previous != null) {
// false表示调用的put()或remove
// true表示因为内存不足,为了腾出空间
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!");
}
// 如果缓存大小size小于配置的最大缓存,则跳出循环
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) {
// 获取对应的缓存对象
// get()方法会将访问的元素更新到队列的尾部
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
// 不存在缓存对象,则创建,create方法是空方法,可以自行实现
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
// 将创建的对象放到缓存里
mapValue = map.put(key, createdValue);
// 如果缓存中存在该对象,则恢复缓存中的对象
if (mapValue != null) {
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
LinkedHashMap的get()方法:
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
// 重新排序该对象,将该对象放到队列尾部
afterNodeAccess(e);
return e.value;
}
afterNodeAccess()方法:
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
参考链接
- https://www.jianshu.com/p/b49a111147ee
- https://github.com/LittleFriendsGroup/AndroidSdkSourceAnalysis/blob/master/article/LruCache%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.md
- https://blog.csdn.net/maosidiaoxian/article/details/51393753
- Flash/Flex学习笔记(34):AS3中的自定义事件
- 字符串处理总结(旧)
- 计算某年某月的某一天是星期几的算法
- Flash/Flex学习笔记(33):如何用As3协同Flash CS IDE控制MovieClip实例
- live writer的折腾
- 用C语言写的万年历---亲手写的。好累哦
- Flash/Flex学习笔记(32):播放音乐并同步显示lyc歌词(适用于Silverlight)
- Flash/Flex学习笔记(31):对象拖拽与投掷
- 如何评价张小龙在2016微信公开课的演讲
- VS2010如何做WAP开发
- (原创)详解KMP算法
- Flash/Flex学习笔记(30):不用startDrag和stopDrag的对象拖动
- JS代码和网页的混用
- Flash/Flex学习笔记(29):MovieClip帧/时间轴的控制
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- nginx如何限制并发连接请求数?
- RTSP协议视频平台EasyNVR证书配置界面上传文件地址自动填写错误怎么处理?
- 开发RTSP/RTMP/GB28181/海康SDK/EHome视频融合平台EasyCVR,使用vue-cli3项目搭建多页面模式的方法
- 互联网视频直播&点播平台RTMP推流组件EasyRTMP在弱网环境下推流稳定吗?会不会有推流失败的问题?
- 安防融合视频云服务EasyCVR集成海康EHome协议实现设备录像回看返回会话ID为-1是什么情况?
- git .gitignore 忽略规则的匹配语法
- vue 初始化高德地图
- js -- 判断数组是否为空?
- 手机没网了,却还能支付,这是什么原理?
- grub异常
- 34.Python字符串替换方法translate
- 35.Python字符串格式运算符%
- 好一个 Spring Boot 开源在线考试系统!解决了我的燃眉之急
- OC基础--数据类型与表达式
- OC基础--字符串