您的位置:首页 > 其它

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
方法移除一个元素。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: