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> { / ...
随机推荐
- vue中的computed和watch区别
在vue.js官方文档中看到computed和watch获取全名的一个例子: var var vm = new Vue({ el: '#demo', data: { firstName: 'Foo', ...
- presto,dremio,spark-sql与ranger的整合记录
dremio,spark-sql,presto和ranger的整合 当前,ranger没有现成的插件来管理dremio,spark-sql,presto. 暂时使用的方法是新建一个用户,如presto ...
- python单例设计模式
class Dog(object): __instance = None def __init__(self): pass def __new__(cls): if not cls.__instanc ...
- 优化器,SGD+Momentum;Adagrad;RMSProp;Adam
Optimization 随机梯度下降(SGD): 当损失函数在一个方向很敏感在另一个方向不敏感时,会产生上面的问题,红色的点以“Z”字形梯度下降,而不是以最短距离下降:这种情况在高维空间更加普遍. ...
- Visual Studio 2017 安装心得
既然VS2017已经发布了,就想安装一下试试,先卸载VS2015, 网上有个完全卸载的东东,https://github.com/Microsoft/VisualStudioUninstaller/r ...
- MyBatis学习与使用(一)
写在前面—— 用 MyBatis 也做过几个项目了,但是一直没有很深入的去理解这个框架,最近决定从头开始学习和整理MyBatis. 之前开发的项目并不是我先创建的,等我介入的时候发现他们已经稍稍封装了 ...
- $[NOIp2017]$ 宝藏 状压$dp$
\(Sol\) 觉得这里是个很巧妙的地方吖,就是记下当前扩展点集的最大深度,然后强制下一步扩展的点集都是最大深度+1.这样做在当前看可能会导致误算答案导致答案偏大,但是整个\(dp\)完成后一定可以得 ...
- $CF311B\ Cats\ Transport$ 斜率优化
AcWing Description Sol 设f[i][j]表示前i个饲养员接走前j只猫咪的最小等待时间. 要接到j猫咪,饲养员的最早出发时间是可求的,设为d: $ d[j]=Tj-\sum_{k= ...
- bat无法识别命令、无法识别运算符、结果不符合预期等问题
1.无法识别的命令等可能为中文字符编码等问题,破坏了bat文件格式,建议去掉中文,加上引号或者重新创建文件.例如:@echo off%~d0cd /d %~dp0title epoch时间转换(%cd ...
- django 数据库连接出现的问题
mysqlclient 1.3.3 or newer is required; you have 0.7.11: 解决方法: 将报错文件中的如下代码注释: if version < (1, 3, ...