最近研究了一下LruCahce
的实现原理,以前也看过几遍源码了,但是有些还是没有理解清楚。重新撸了一遍代码,吼吼吼。
private final LinkedHashMap 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(0, 0.75f, true);
}
这里设置了maxSize
,以及实例化了一个LinkedHashMap
对象,这个LinkedHashMap
对象是实现Lru
算法的关键,Lru
是最近最少使用算法的简称,意思呢就是查询出最近的时间使用次数最少的那个对象。
new LinkedHashMap
这句代码表示,初始容量为零,0.75
是加载因子,表示容量达到最大容量的75%的时候会把内存增加一半。最后这个参数至关重要。表示访问元素的排序方式,true
表示按照访问顺序排序,false
表示按照插入的顺序排序。
我这里写了一个小程序专门研究这两个参数的不同之处。
当设置为true
的时候:
public static final void main(String[] args) {
LinkedHashMap 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 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 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
方法移除一个元素。