缓存(LruCache)机制
LruCache
1.变量
private final LinkedHashMap<K, V> map; private int size;//已经存储的数据大小
private int maxSize;//最大存储大小 private int putCount;//调用put的次数
private int createCount;//调用create的次数
private int evictionCount;//收回的次数
private int hitCount;//取出数据的成功次数
private int missCount;//取出数据的丢失次数
2.构造函数
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);
}
3.保存到缓存
public final V put(K key, V value) {
...
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;
}
safeSizeOf()
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
sizeOf()
protected int sizeOf(K key, V value) {
return 1;
}
trimToSize()
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
...
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);
}
}
entryRemoved()是空函数
4.从缓存中取
public final V get(K key) {
...
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
} 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;
}
}
其中,map.put/get调用的都是LinkedHashMap中的方法,下面我们来看
LinkedHashMap
1.构造函数
public LinkedHashMap() {
init();
accessOrder = false;
} public LinkedHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
} public LinkedHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, false);
} public LinkedHashMap(
int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
init();
this.accessOrder = accessOrder;
} public LinkedHashMap(Map<? extends K, ? extends V> map) {
this(capacityForInitSize(map.size()));
constructorPutAll(map);
}
LruCache构造函数中调用的是
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
init()
@Override void init() {
header = new LinkedEntry<K, V>();
}
static class LinkedEntry<K, V> extends HashMapEntry<K, V> {
LinkedEntry<K, V> nxt;
LinkedEntry<K, V> prv; /** Create the header entry */
LinkedEntry() {
super(null, null, 0, null);
nxt = prv = this;
} /** Create a normal entry */
LinkedEntry(K key, V value, int hash, HashMapEntry<K, V> next,
LinkedEntry<K, V> nxt, LinkedEntry<K, V> prv) {
super(key, value, hash, next);
this.nxt = nxt;
this.prv = prv;
}
}
2.获取缓存
LruCache中的put调用的就是HashMap的put,get方法在LinkedHashMap中复写
@Override public V get(Object key) {
/*
* This method is overridden to eliminate the need for a polymorphic
* invocation in superclass at the expense of code duplication.
*/
if (key == null) {
HashMapEntry<K, V> e = entryForNullKey;
if (e == null)
return null;
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
} int hash = Collections.secondaryHash(key);
HashMapEntry<K, V>[] tab = table;
for (HashMapEntry<K, V> e = tab[hash & (tab.length - 1)];
e != null; e = e.next) {
K eKey = e.key;
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
if (accessOrder)
makeTail((LinkedEntry<K, V>) e);
return e.value;
}
}
return null;
}
makeTail()
private void makeTail(LinkedEntry<K, V> e) {
// Unlink e
e.prv.nxt = e.nxt;
e.nxt.prv = e.prv; // Relink e as tail
LinkedEntry<K, V> header = this.header;
LinkedEntry<K, V> oldTail = header.prv;
e.nxt = header;
e.prv = oldTail;
oldTail.nxt = header.prv = e;
modCount++;
}
这样,键值对被剥离出来,放到了链表的尾巴上。这样的结果就是不管用户是调用get()
还是put()
都会将操作的那个实体放在链表的最后位置,那么最不常用的就会放在最首位的下一个节点,对应的实体为header.nex
,也就是放在header
的下一节点.
总结:
1.LruCache 是通过 LinkedHashMap 构造方法的第三个参数的
accessOrder=true
实现了LinkedHashMap
的数据排序基于访问顺序 (最近访问的数据会在链表尾部),在容量溢出的时候,将链表头部的数据移除。从而,实现了 LRU 数据缓存机制。**2.**LruCache 在内部的get、put、remove包括 trimToSize 都是安全的(因为都上锁了)。
**3.**LruCache 自身并没有释放内存,将 LinkedHashMap 的数据移除了,如果数据还在别的地方被引用了,还是有泄漏问题,还需要手动释放内存。
**4.**覆写
entryRemoved
方法能知道 LruCache 数据移除是是否发生了冲突,也可以去手动释放资源。5.
maxSize
和sizeOf(K key, V value)
方法的覆写息息相关,必须相同单位。( 比如 maxSize 是7MB,自定义的 sizeOf 计算每个数据大小的时候必须能算出与MB之间有联系的单位 )
ps:纯原创,参考文章如下
面试回答:
1)LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用
2)如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。maxSize是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少
3)size在添加和移除缓存都被更新值,他通过safeSizeOf这个方法更新值。safeSizeOf默认返回1,但一般我们会根据maxSize重写这个方法,比如认为mazSize代表是KB的话,那么就以KB为单位返回该项所占的内存大小
4)除异常外,首先会判断size是否超过maxSize,如果超过了就取出最先插入的缓存,如果不为空就删掉,并把size减去该项所占的大小。这个操作将一直循环下去,直到size比maxSize小或者缓存为空
缓存(LruCache)机制的更多相关文章
- Android-Universal-Image-Loader的缓存处理机制与使用 LruCache 缓存图片
讲到缓存,平时流水线上的码农一定觉得这是一个高大上的东西.看过网上各种讲缓存原理的文章,总感觉那些文章讲的就是玩具,能用吗?这次我将带你一起看过UIL这个国内外大牛都追捧的图片缓存类库的缓存处理机制. ...
- Android-Universal-Image-Loader的缓存处理机制
讲到缓存,平时流水线上的码农一定觉得这是一个高大上的东西.看过网上各种讲缓存原理的文章,总感觉那些文章讲的就是玩具,能用吗?这次我将带你一起看过UIL这个国内外大牛都追捧的图片缓存类库的缓存处理机制. ...
- Redis 的缓存淘汰机制(Eviction)
本文从源码层面分析了 redis 的缓存淘汰机制,并在文章末尾描述使用 Java 实现的思路,以供参考. 相关配置 为了适配用作缓存的场景,redis 支持缓存淘汰(eviction)并提供相应的了配 ...
- Mybatis缓存处理机制
一.MyBatis缓存介绍 正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Se ...
- 并发读写缓存实现机制(一):为什么ConcurrentHashMap可以这么快?
大家都知道ConcurrentHashMap的并发读写速度很快,但为什么它会这么快?这主要归功于其内部数据结构和独特的hash运算以及分离锁的机制.做游戏性能很重要,为了提高数据的读写速度,方法之一就 ...
- 从源代码分析Android-Universal-Image-Loader的缓存处理机制
讲到缓存,平时流水线上的码农一定觉得这是一个高大上的东西.看过网上各种讲缓存原理的文章,总感觉那些文章讲的就是玩具,能用吗?这次我将带你一起看过UIL这个国内外大牛都追捧的图片缓存类库的缓存处理机制. ...
- Hibernate中的脏检查和缓存清理机制
脏检查 Session到底是如何进行脏检查的呢?当一个Customer对象被加入到Session缓存中时,Session会为Customer对象的值类型的属性复制一份快照.当Session清理缓存时, ...
- Hibernate——脏检查和缓存清理机制
Session到底是如何进行脏检查的呢? 当一个Customer对象被加入到Session缓存中时,Session会为Customer对象的值类型的属性复制一份快照.当Session清理缓存时,会先进 ...
- Cache【硬盘缓存工具类(包含内存缓存LruCache和磁盘缓存DiskLruCache)】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 内存缓存LruCache和磁盘缓存DiskLruCache的封装类,主要用于图片缓存. 效果图 代码分析 内存缓存LruCache和 ...
- 使用Spring提供的缓存抽象机制整合EHCache为项目提供二级缓存
Spring自身并没有实现缓存解决方案,但是对缓存管理功能提供了声明式的支持,能够与多种流行的缓存实现进行集成. Spring Cache是作用在方法上的(不能理解为只注解在方法上),其核心思想是 ...
随机推荐
- Java设计模式の工厂模式
-------------------------------------------------------- 目录: 一.序言 二.简单工厂模式 三.工厂方法模式 四.简单工厂和工厂方法模式的比 ...
- jsp04状态管理
1.http 协议的无状态性 无状态是指,当浏览器发送请求给服务器的时候,服务器会响应.但当同一个浏览器再次发送请求时,服务器不会知道是刚才那个浏览器. 简单说,服务器[不会保存用户状态],不会记得客 ...
- 彻底解决_OBJC_CLASS_$_某文件名", referenced from:问题
最近在使用静态库时,总是出现这个问题.下面总结一下我得解决方法: 1. .m文件没有导入 在Build Phases里的Compile Sources 中添加报错的文件 2. .framewor ...
- jQuery实现用户头像裁剪插件cropbox.js
几乎每一个网页是必备图片上传,图片裁剪功能,这里通过cropbox.js插件实现该功能. <script src="js/jquery-1.11.1.min.js">& ...
- 直观理解js自执行函数
要在函数体后面加括号就能立即调用,则这个函数必须是函数表达式,不能是函数声明: Jslint推荐的写法: (function(){alert(1);}()); 针对函数声明,使用().!.+.-.=. ...
- Python脚本 - 查询磁盘的读写次数信息
测试系统为:Centos 6.7 Python版本为: 3.6.4 脚本功能:查看指定磁盘的读写及时间等相关信息 #!/usr/bin/env python3 from collections imp ...
- net_device->uc_promisc
如果设备不支持单播过滤,并且要监听多个单播地址时,就要使用net_device->uc_count和net_device->uc_promisc来设置混杂模式,具体见__dev_set_r ...
- Linux 入门记录:十八、Linux 系统启动流程 + 单用户修改 root 密码 + GRUB 加密
一.系统启动流程 一般来说,Linux 系统的启动流程是这样的: 1. 开机之后,位于计算机主板 ROM 芯片上的 BIOS 被最先读取,在进行硬件和内存的校验以及 CPU 的自检没有异常后, BIO ...
- python基础===python os.path模块
os.path.abspath(path) #返回绝对路径 os.path.basename(path) #返回文件名 os.path.commonprefix(list) #返回list(多个路径) ...
- scrapy抓取学院新闻报告
抓取四川大学公共管理学院官网(http://ggglxy.scu.edu.cn)所有的新闻咨询. 实验流程 1.确定抓取目标.2.制定抓取规则.3.'编写/调试'抓取规则.4.获得抓取数据 1.确定抓 ...