我在参加笔试的时候,有一道题是设计一个 LruCache,当时由于不理解原理而没有写出来,现在看了几遍源码,记录下笔记理清思路。
LruCache 的底层实现是 LinkedHashMap 。
先看到 LruCache 的构造方法:
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap(0, 0.75f, true);
}
配置了缓存的最大值,然后创建了一个 LinkedHashMap 对象,如果你了解 LinkedHashMap,会知道这个 Map 是可以实现 LRU 算法的。
而这里恰好开启了 LRU 算法,没错,LinkedHashMap 构造方法第三个参数传进 true 就是了。
我们来看 LruCache 的 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++;
size += safeSizeOf(key, value);
previous = map.put(key, value);//添加缓存
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);//整理缓存
return previous;
}
可以看到,key 和 value 都不允许为 null。在 synchronized 代码块中,调用了 LinkedHashMap 的 put 方法将 key 和 value 传进,缓存起来。而且每一次调用 put 方法,都会调用 trimToSize 方法。
你应该和我一样关心 LruCache 是怎么做到容量满了就移除缓存的。
我们重点关注到 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) {
break;//缓存未满,跳出循环体
}
Map.Entry toEvict = map.eldest();//取出最近最少使用的元素
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
看到这一行核心代码
Map.Entry<K, V> toEvict = map.eldest()
ps:Evict 是逐出的意思
可以看到这里调用了 LinkedHashMap 的 eldest 方法,
public Map.Entry<K, V> eldest() {
Entry<K, V> eldest = header.after;
return eldest != header ? eldest : null;
}
这个方法取出了 LinkedHashMap 的双向链表头节点的下一个节点。
补充一下LinkedHashMap:
LinkedHashMap 在开启 LRU 算法后,每次调用 put 方法和 get 方法,都会将(最近最常访问的)元素放在链接的结尾。也就是说,多次调用 LinkedHashMap 的 put 和 get 方法,位于链表前面的元素,就是最近最少使用的了,应该被移除。
而这里取出头节点的下一个节点,肯定是移除它的啦。这也是合理的。好,我们继续看到 LruCache 的 trimToSize 方法,之后就是调用了 HashMap 的 remove 方法来移除元素了。(LinkedHashMap 是继承自 HashMap 的)
全文完。