Java容器解析系列(17) LruCache详解
在之前讲LinkedHashMap的时候,我们说起可以用来实现LRU(least recent used)算法,接下来我看一下其中的一个具体实现-----android sdk 中的LruCache.
关于Lru算法,请参考漫画:什么是LRU算法?
talk is cheap, I am gonna show you something really expensive.
package android.util;// 该类是从Android sdk 中摘录
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;// 这里的 LinkedHashMap 为 android.jar 中的类,与jdk中的有所区别,这里不做区分
private int size;// 当前 cache 的大小
private int maxSize;// cache 最大容量
private int putCount;// put() 调用的次数
private int createCount;// get()未命中,成功构建新 key:value 的次数
private int evictionCount;// 因cache大小超过容量限制,将 key:value 从 cache 中驱逐的次数
private int hitCount;// get()命中的次数
private int missCount;// get()未命中的次数
// 构造时设置最大容量
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
// 这里传入的 LinkedHashMap 的 accessOrder 为true,表示 其中的链表中的结点顺序为 "按访问顺序"
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
// 重新设置 cache 最大容量
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
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++;// 命中次数 + 1
return mapValue;
}
missCount++;// 未命中次数 + 1
}
V createdValue = create(key);// 通过 key 构建一个value(比如从文件中读入value)
if (createdValue == null) {// 创建失败
return null;
}
synchronized (this) {
createCount++;// 构建 value 成功次数 + 1
mapValue = map.put(key, createdValue);// 添加到 LinkedHashMap 中
if (mapValue != null) {
// LinkedHashMap 原来有该key的值(在构建value的时候,被其他线程添加进去的),这里重新把原来的值放进去,cache大小没有增加
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);// cache 大小增加
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);// 大小增加了,保证在最大容量范围内
return createdValue;
}
}
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++;// put 次数
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);// 替换已有的value
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
// 移除LRU键值对,保证 cache 的大小在 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) {
break;
}
Map.Entry<K, V> toEvict = map.eldest();// 最老的结点,返回head结点,也即 LRU结点
if (toEvict == null) {// 没有可以删除的key:value了
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);// 大小降低
evictionCount++;// 驱逐次数 + 1
}
entryRemoved(true, key, value, null);
}
}
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V previous;
synchronized (this) {
previous = map.remove(key);// 通过 LinkedHashMap 移除
if (previous != null) {
size -= safeSizeOf(key, previous);// cache总大小降低
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
// key:oldValue 被 移除 或 驱逐 时回调
protected void entryRemoved(boolean evicted/*是否是被驱逐(因cache大小超过容量限制被删除)*/,
K key, V oldValue, V newValue) {
}
// 根据指定 key,构建 value
protected V create(K key) {
return null;
}
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
// key:value 键值对大小,用于计算cache大小
// 可根据情况自定义,默认为 1
// 该 key:value 在 cache 中时大小不应该变化
protected int sizeOf(K key, V value) {
return 1;
}
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
// 各种方法,返回成员变量,省略
public synchronized final Map<K, V> snapshot() {
return new LinkedHashMap<K, V>(map);
}
@Override
public synchronized final String toString() {
int accesses = hitCount + missCount;
int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
maxSize, hitCount, missCount, hitPercent);
}
}
Java容器解析系列(17) LruCache详解的更多相关文章
- Java容器解析系列(11) HashMap 详解
本篇我们来介绍一个最常用的Map结构--HashMap 关于HashMap,关于其基本原理,网上对其进行讲解的博客非常多,且很多都写的比较好,所以.... 这里直接贴上地址: 关于hash算法: Ha ...
- Java容器解析系列(13) WeakHashMap详解
关于WeakHashMap其实没有太多可说的,其与HashMap大致相同,区别就在于: 对每个key的引用方式为弱引用; 关于java4种引用方式,参考java Reference 网上很多说 弱引用 ...
- Java容器解析系列(7) ArrayDeque 详解
ArrayDeque,从名字上就可以看出来,其是通过数组实现的双端队列,我们先来看其源码: /** 有自动扩容机制; 不是线程安全的; 不允许添加null; 作为栈使用时比java.util.Stac ...
- Java容器解析系列(9) PrioriyQueue详解
PriorityQueue:优先级队列; 在介绍该类之前,我们需要先了解一种数据结构--堆,在有些书上也直接称之为优先队列: 堆(Heap)是是具有下列性质的完全二叉树:每个结点的值都 >= 其 ...
- Java容器解析系列(14) IdentityHashMap详解
IdentityHashMap,使用什么的跟HashMap相同,主要不同点在于: 数据结构:使用一个数组table来存储 key:value,table[2k] 为key, table[2k + 1] ...
- Java容器解析系列(12) LinkedHashMap 详解
LinkedHashMap继承自HashMap,除了提供HashMap的功能外,LinkedHashMap还是维护一个双向链表(实际为带头结点的双向循环链表),持有所有的键值对的引用: 这个双向链表定 ...
- Java容器解析系列(0) 开篇
最近刚好学习完成数据结构与算法相关内容: Data-Structures-and-Algorithm-Analysis 想结合Java中的容器类加深一下理解,因为之前对Java的容器类理解不是很深刻, ...
- Java容器解析系列(10) Map AbstractMap 详解
前面介绍了List和Queue相关源码,这篇开始,我们先来学习一种java集合中的除Collection外的另一个分支------Map,这一分支的类图结构如下: 这里为什么不先介绍Set相关:因为很 ...
- Java容器解析系列(6) Queue Deque AbstractQueue 详解
首先我们来看一下Queue接口: /** * @since 1.5 */ public interface Queue<E> extends Collection<E> { / ...
随机推荐
- 为什么我们要使用DTO
基础结构解释 UI-表现层-与控制器打交道(UI向Controller 传递数据时使用DTO(数据传输对象)) Service-应用服务层 Domain 领域对象 DTO 数据传输对象,一般只包含基础 ...
- springcloud玩转单点登录【oauth】
随着公司项目的庞大,单点登录变得尤为重要,那么怎么实现单点登录,下面已oauth为标准实现单点登录. [特别鸣谢:魔乐科技,附上官网:www.mldn.cn] 1:项目组织结构 本项目为oAuth修改 ...
- 004使用u-boot烧写裸版程序
- rapidxml遍历子节点例子
<?xml version="1.0" encoding="utf-8"?> <luaword> <btn> <inf ...
- 智能反射表面(可重构智能表面)Large Intelligent surface 最新综述整理
闻道洛阳花正好,家家遮户春风.道人饮处百壶空.年年花下醉,看尽几番红. 此拐又从何处去,飘蓬一任西东.语声虽异笑声同.一轮清夜月,何处不相逢. ---- 临江仙·与刘拐 更多精彩内容请关注微信公众号 ...
- 1026 程序运行时间 (15 分)C语言
题目描述 要获得一个C语言程序的运行时间,常用的方法是调用头文件time.h,其中提供了clock()函数,可以捕捉从程序开始运行到clock()被调用时所耗费的时间.这个时间单位是clock tic ...
- Theia APIs——Preferences
上一篇:Theia APIs——命令和快捷键 Preferences Theia有一个preference service,模块可以通过它来获取preference的值,提供默认的preference ...
- mysql oracle sql获取树 父级 子级 及自己
select * from ( select t.*,d.TABLE_NAME,d.QUERY_SQL,d.data_control_col,d.id table_id,d.where_sql fro ...
- 变量键盘读取、数组与宣告:read,array,declare
1.read 2.declare/typeset 宣告变量的类型 3.数组(array)变量类型 4.与文件系统及程序的限制关系:ulimit 限制用户的某些系统资源,包括,可以开启的文件的数量,可以 ...
- C++指针声明
指针声明 void f(int) void (*p1)(int)=&f; void (*p2)(int)=f; 调用例子: int f(); int (*p) ()=f; //指针p指向f i ...