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> { / ...
随机推荐
- com.netflix.discovery.DiscoveryClient : Completed shut down of DiscoveryClient
启动报错:com.netflix.discovery.DiscoveryClient : Completed shut down of DiscoveryClient 解决方案: 添加web主件 ...
- The Zen of Python —— Python 之禅
Beautiful is better than ugly. # 优美好于丑陋(Python以编写优美的代码为目标) Explicit is better than implicit. # 明 ...
- Team Foundation Server 2015使用教程【7】:权限为读取器的团队成员连接tfs及checkin操作
- 【Linux】Linux下date,time等时间设置
date命令的帮助信息 [root@localhost source]# date --help用法:date [选项]... [+格式] 或:date [-u|--utc|--universal] ...
- 22.json&pickle&shelve
转载:https://www.cnblogs.com/yuanchenqi/article/5732581.html json 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...
- The first day of Crawler learning
使用BeautifulSoup解析网页 Soup = BeautifulSoup(urlopen(html),'lxml') Soup为汤,html为食材,lxml为菜谱 from bs4 impor ...
- 004 ceph存储池
介绍:前面已经使用deploy和ansble部署了ceph集群,现在对集群的池进行简单介绍和简单操作 一.部分概念 池是ceph存储集群的逻辑分区,用于存储对象 对象存储到池中时,使用CRUSH规则将 ...
- 洛谷$1541$ 乌龟棋 线性$DP$
Luogu CH Sol f[i]表示走到第i个格子时获得的最大分数 发现转移与各个爬行卡片的数量有关,一共只有4种卡片 所以就把这四种卡片的已使用张数也放进状态,f[i][a][b][c][d] ...
- Spring 资源注入
Spring开发中经常需要调用各种资源,包含普通文件.网址.配置文件.系统环境变量等,我们可以使用Spring表达式语言(Spring-EL)实现资源的注入. Spring主要使用@Value注解实现 ...
- VS Code 解决 因为在此系统上禁止运行脚本
vscode执行命令的 主要是由于没有权限执行脚本.开通权限就可以解决啦 在搜索框中输入:powerShell 选择管理员身份运行 输入命令行:set-ExecutionPolicy RemoteSi ...