LRU 全称是 Least Recently Used,即最近最久未使用算法,它是页面置换算法的一种。
原理
如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。
LinkedHashMap介绍
LinkedHashMap继承于HashMap,与HashMap的区别是LinkedHashMap是有序的。
LinkedHashMap内部采用双向链表来记录插入顺序:
transient LinkedHashMapEntry<K,V> head;
transient LinkedHashMapEntry<K,V> tail;
LinkedHashMap并没有重写HashMap的put(key, value)方法,而是重写了newNode(hash, key, value, e),replacementNode(p, next),newTreeNode(hash, key, value, next),replacementTreeNode(p, next)方法,将key value插入到双向链表中。
LinkedHashMap重写了HashMap的get(key)方法,当accessOrder为true时,会将Node移至链表尾。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
LruCache
LruCache内部使用LinkedHashMap实现。
在LruCache的构造方法中会创建LinkedHashMap,其中accessOrder默认为true,这也就保证了在get(key)过程后会将结点放置队尾。
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;
public LruCache(int maxSize) {
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
public void resize(int maxSize) {}
public final V get(K key) {}
public final V put(K key, V value) {}
public void trimToSize(int maxSize) {}
public void V remove(K key) {}
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
protected V create(K key) {}
protected int sizeOf(K key, V value) {}
}
这里主要介绍下put(key, value)和get(key)两个方法。
put的过程很简单,如果map中有旧值,则回调entryRemoved(evicted, key, oldValue, newValue)方法。
最后会调用trimToSize(maxSize)方法,如果大小超过maxSize值,则会从头部移除结点。
public final V put(K key, V value) {
V previous;
synchronized (this) {
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;
}
get的过程稍微复杂些,如果取到了value则直接返回。如果没取到,则调用create(key)方法尝试创建默认值,还要规避下冲突的情况。
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++;
return mapValue;
}
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.
*/
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;
}
}