文章目錄
  1. 1. 初始化
  2. 2. trimToSize () 核心代码
  3. 3. put()
  4. 4. get()
  5. 5. remove()
  6. 6. 结语

转载请附原文链接: LruCache 源码学习笔记

上一篇笔记分析了 Volley 中的 ImageLoder ,作者推荐使用 LruCache 来作为一级缓存。本篇笔记就来分析一下 LruCache 是如何实现缓存的。如果你对 LruCache 还不了解,建议先学会使用,本篇笔记直接上源码,不会介绍使用。

LruCache 是 Android 中的一个缓存工具类,在 Android 3.1 的时候加入 util 包中,对于低版本的上可以在 support 包中找到这个类。该类以 key-value 的形式保存数据,不允许 null key 或者是 null value ,因此只要get(key) 只要返回空,说明缓存中没有这个 key 对应的数据。

LruCache 实际上是 LinkedHashMap + 最近使用算法,下面就进入源码中去

初始化

构造方法指定最大容量,初始化一个 LinedHashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private int size; // 但前大小
private int maxSize;// 允许的最大值
private int putCount;// 放入缓存数
private int createCount;// 创建数据数
private int evictionCount;// 移除数目
private int hitCount;// 命中数
private int missCount;// miss数
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);
}

在继承了该类的时候,应该重写 sizeOf 方法来确定每一个元素的大小,默认返回是1

1
2
3
protected int sizeOf(K key, V value) {
return 1;
}

trimToSize () 核心代码

LruCache 中有一个方法 trimToSize() ,这个方法就是根据允许的最大容量来调整存储的元素,是否需要清理掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void trimToSize(int maxSize) {
// 首先进入一个循环
while (true) {
K key;
V value;
synchronized (this) {
// 如果当前的容量小于0,或者实际为空但 size不为0 抛异常
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();
if (toEvict == null) {
break;
}
// 从集合移除这个元素,重新计算当前大小,移除的数量+1
// 进入下一次循环判断
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}

上面就是调整缓存空间的核心代码,总共没有几行。剩下的就是简单的查找,增加,移除等等操作

put()

在往缓存中放数据的时候,考虑了一个冲突的问题,可能这个值已经缓存过了,默认采用的是覆盖,但是提供了一个方法来自定义是覆盖还是保留。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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++;
// 增加当前大小
size += safeSizeOf(key, value);
// 缓存到map中,并获取是否有冲突的值
previous = map.put(key, value);
if (previous != null) {
// 如果之前已经缓存过了,那只是覆盖,因此需要把之前这个值的大小减去
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
// 该方法提供给使用者自定义是覆盖还是取消
entryRemoved(false, key, previous, value);
}
// 检查大小并调整
trimToSize(maxSize);
// 返回冲突的值
return previous;
}

在 LruCache 中只要有元素被移除就会调用一个方法 entryRemoved(),复写这个方法可以自定义你自己的冲突处理策略。

1
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}

get()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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) {
// 找到后 命中数+1 返回这个元素
hitCount++;
return mapValue;
}
// 没找到就把 miss 数+1
missCount++;
}
// 以下的代码会试图创建一个数据返回
/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/
// 如果创建的元素为 null 直接返回 null
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
// 说明创建元素成功
createCount++;
// 添加到缓存中
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last 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;
}
}

可以看到 LruCache 在获取缓存失败了之后,还提供了创建的方法。

1
2
3
protected V create(K key) {
return null;
}

remove()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}

结语

LruCache 的源码就分析完了,发现其实原理没有想象的复杂,事实上有很多牛逼的框架原理也没有想象的那么难,可能就是用最简单的逻辑堆叠而成。