LruCache 实现原理分析
2016-07-13 13:36
543 查看
LruCache 实现原理分析
最近研究了一下LruCahce的实现原理,以前也看过几遍源码了,但是有些还是没有理解清楚。重新撸了一遍代码,吼吼吼。
声明的变量
private final LinkedHashMap<K, V> map; /** Size of this cache in units. Not necessarily the number of elements. */ private int size; private int maxSize; private int putCount; private int createCount; private int evictionCount; private int hitCount; private int missCount;
map:存放数据的集合
size:当前
LruCahce的内存占用大小
maxSize:
Lrucache的最大容量
putCount:
put的次数
createCount:
create的次数
evictionCount:回收的次数
hitCount:命中的次数
missCount:丢失的次数
构造函数:
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); }
这里设置了
maxSize,以及实例化了一个
LinkedHashMap对象,这个
LinkedHashMap对象是实现
Lru算法的关键,
Lru是最近最少使用算法的简称,意思呢就是查询出最近的时间使用次数最少的那个对象。
new LinkedHashMap<K, V>(0, 0.75f, true)这句代码表示,初始容量为零,
0.75是加载因子,表示容量达到最大容量的75%的时候会把内存增加一半。最后这个参数至关重要。表示访问元素的排序方式,
true表示按照访问顺序排序,
false表示按照插入的顺序排序。
我这里写了一个小程序专门研究这两个参数的不同之处。
当设置为
true的时候:
public static final void main(String[] args) { LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true); map.put(0, 0); map.put(1, 1); map.put(2, 2); map.put(3, 3); map.put(4, 4); map.put(5, 5); map.put(6, 6); map.get(1); map.get(2); for (Map.Entry<Integer, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ":" + entry.getValue()); } }
输出结果是:
0:0 3:3 4:4 5:5 6:6 1:1 2:2
当设置为
false的时候,输出顺序为:
0:0 1:1 2:2 3:3 4:4 5:5 6:6
有以上结果可以看出,这个设置为
true的时候,如果对一个元素进行了操作
(put、get),就会把那个元素放到集合的最后,设置为
false的时候,无论怎么操作,集合元素的顺序都是按照插入的顺序来进行存储的。
到了这里我们可以知道,这个
LinkedHashmap正是实现
Lru算法的核心之处,当内容容量达到最大值的时候,只需要移除这个集合的前面的元素直到集合的容量足够存储数据的时候就可以了。
下面我们来看一看
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) { putCount++; //put的次数+1 size += safeSizeOf(key, value); //把当前容量增加,增加值为value的大小 previous = map.put(key, value); //previous为旧的值 if (previous != null) { size -= safeSizeOf(key, previous); //如果旧的值不为空,就把旧的值得大小减去 } } if (previous != null) { entryRemoved(false, key, previous, value); } trimToSize(maxSize); return previous; }
由上面的代码可以看出来,首先把
size增加,然后判断是否以前已经有元素,如果有,就更新当前的
size,并且调用
entryRemoved方法,
entryRemoved是一个空实现,如果我们使用
LruCache的时候需要掌握元素移除的信息,可以重写这个方法。最后就会调用
trimToSize,来调整集合中的内容。
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!"); } if (size <= maxSize || map.isEmpty()) { break; } Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } }
这个方法是一个无限循环,跳出循环的条件是,
size < maxSize或者
map 为空。主要的功能是判断当前容量时候已经超出最大的容量,如果超出了
maxSize的话,就会循环移除
map中的第一个元素,直到达到跳出循环的条件。由上面的分析知道,
map中的第一个元素就是最近最少使用的那个元素。
研究完了
put方法之后,下面开始研究
get方法。
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); //创建 if (createdValue == null) { return null; } synchronized (this) { createCount++; //创建 + 1 mapValue = map.put(key, createdValue); if (mapValue != null) { // There was a conflict so undo that last put //如果有矛盾,意思就是有旧的值,就撤销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; } }
这个方法就先通过
key来获取
value,如果能获取到就就直接返回,获取不到的话,就调用
create()方法创建一个,事实上,如果我们不重写这个
create方法的话是
return null的,所以整个流程就是获取得到就直接返回,获取不到就返回
null。至于后面那段代码呢?我看了几遍也没理解是适合什么场景的。反正就是重写的
create方法之后就会执行后面的代码,不过我们通常使用的时候都是没有重写这个方法的。
最后说一下
remove方法:
public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } V previous; synchronized (this) { previous = map.remove(key); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, null); } return previous; }
remove方法没什么可以研究的了,就是使用
remove方法移除一个元素。
相关文章推荐
- LruCache 实现原理分析
- swift中获取命名空间
- Ring buffer basics 环形缓冲基础(C语言实现) 二
- CodeForces 474C Captain Marmot (数学,旋转,暴力)
- UIWebView保存图片
- java.util.concurrent
- [压缩]C#下使用SevenZipSharp压缩解压文本
- C# 委托应用总结
- css第一步
- 多线程设计模式——Master-Salave(主仆)模式
- Dagger2 这次入门就不用放弃了
- STM32F4库函数里面的RCC_DeInit(void)函数分析
- Linux 用户态和内核态
- 《JavaScript语言精粹》学习笔记——4.函数
- 构建可靠的网络服务器之连接的建立和终止
- java 面试心得总结-BAT、网易
- 破解工具之调试器和相关破解案例视频教程大全
- HDU1533||ZQUOJ23130Going Home最小费用最大流
- The user specified as a definer ('root'@'%') does not exist
- Tomcat在Mac系统下的配置