LRU内部可以使用LinkedHashMap实现。
HashMap
HashMap采用数组+单链表实现
LinkedHashMap
LinkedHashMap继承自HashMap,是一种有序的Map。
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}HashMap+LinkedList(双向链表),在原有HashMap数据结构基础上,其构造方式跟HashMap完全一致,在HashMap 的节点的基础上, 加上了两个引用来将所有entry节点串联成一个双向链表。这个顺序默认是数据插入顺序。在构造方法里accessOrder = true,表示按访问顺序。
/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;
//指定遍历LinkedHashMap的顺序,true表示按照访问顺序,false表示按照插入顺序,默认为false
final boolean accessOrder;
LinkedHashMap中没有重写父类的Put方法,因此使用HashMap中的Put方法。但是重写了构建节点方法newNode()。
HashMap Put(k,v)方法代码如下,final关键字表示Put方法是不可以被重写的,可知所有继承自HashMap的类都是直接使用其父类的Put方法。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果是空桶,执行初始化,并且扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果没有Hash碰撞,桶位置没有value,就新建一个Node,放在该bucket位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果Hash值相等,key也相等,直接替换value值
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)//该位置节点是红黑树节点
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//发生hash碰撞,在该bucket节点后面插入一个普通节点
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {//遍历到尾部
p.next = newNode(hash, key, value, null);//新建一个节点放到尾部
//如果追加节点后,链表数量》=8,则转化为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果找到了要覆盖的节点,则p.next在上面已经指向了新加入的节点,并且新加入的节点得到了老节点的引用,只需要替换value值即可。
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;//p指向下一个节点,循环往后找
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;//这时候e拿到的是要替换的节点引用,所以里面的value值是老值
if (!onlyIfAbsent || oldValue == null)
e.value = value;//替换新值
afterNodeAccess(e);
return oldValue;
}
}
//如果执行到这里,说明没找到相同的key,是插入新节点,modeCount+1,注:modeCount表示修改次数,为了保证线程安全
++modCount;
//判断是否需要扩容
if (++size > threshold)
resize();
//空实现,LinkedHashMap重写使用
afterNodeInsertion(evict);
return null;
}在插入的时候LinkedHashMap复写了HashMap的newNode方法,并且在方法内部更新了双向链表的指向关系。
LinkedHashMap中重写newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
//内部使用父类HashMap的构造方法
LinkedHashMapEntry<K,V> p =
new LinkedHashMapEntry<K,V>(hash, key, value, e);
linkNodeLast(p);//把节点添加到双链表的尾部
return p;
}
private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
//如果是空map,则head= tail = p
LinkedHashMapEntry<K,V> last = tail;
tail = p;//tail永远等于最新的节点,有最新节点的引用
if (last == null)
head = p;//head只在空表插入节点时等于最老的节点,因此head永远是最老的
else {
//构建双链表关系
p.before = last;
last.after = p;
}
}HashMap在插入的时候调用了afterNodeInsertion()方法,在HashMap中这个方法是空实现,而在LinkedHashMap中则有具体实现
//是否要移除最老的节点(即Head节点),LRU功能会用到。所以LRU可以使用LinkedHashMap来实现。evict在Put(K,V)中默认为true
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMapEntry<K,V> first;
//
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
//默认不移除
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}如果我们要根据LinkedHashMap 实现一个LruCashed , 我们只需要继承LinkedHashMap ,重写removeEldestEntry, 当当前长度> 缓存长度, 返回true 即可。
LinkedHashMap的get(K key)方法中调用了afterNodeAccess方法。
//将访问过的节点移到双链表队尾,并将引用赋值给tail
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}afterNodeAccess()方法中如果accessOrder=true时会移动节点到双向链表尾部。这就是为什么访问顺序输出时访问到的元素移动到链表尾部的原因。
LRU实现
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}capacity:桶的数量,即桶数组的size
capacity译为容量。capacity就是指HashMap中桶的数量。默认值为16。一般第一次扩容时会扩容到64,之后好像是2倍。总之,容量都是2的幂。
loadFactor
loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。loadFactor的默认值为0.75f。计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。
threshold
threshold表示当HashMap的size大于threshold时会执行resize操作。
threshold=capacity*loadFactor
自定义LruCache:
public class LruCache extends LinkedHashMap<Integer, Integer> {
private int cacheSize;
public LruCache(int cacheSize){
super(0,0.75f,true);
this.cacheSize = cacheSize;
}
public static void main(String[] args) {
LruCache lruCache = new LruCache(3);
lruCache.put(1, 1);
lruCache.put(2, 2);
lruCache.put(3, 3);
lruCache.put(4, 4);
lruCache.put(5, 5);
for(Map.Entry<Integer, Integer> entry: lruCache.entrySet()){
System.out.println(entry.getKey());
}
}
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<Integer, Integer> eldest) {
// TODO Auto-generated method stub
return size()>cacheSize;
}
}
输出结果为:
3
4
5