LRU:
Least Recently Used 最近最久未使用。缓存算法。
com.mysql.jdbc.util.LRUCache 最简化版的继承LinkedHashMap<Object, Object>
org.apache.dubbo.common.utils.LRUCache 继承LinkedHashMap,加了一个成员变量Lock可重入锁,所有的增删改查清都显示lock()/unlock()
借助LinkedHashMap实现:
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache2 extends LinkedHashMap {
private int maxSize;
public LRUCache2(int size) {
// int initialCapacity, float loadFactor, boolean accessOrder
super(size, 0.75f, true);
maxSize = size;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
// 重写removeEldestEntry方法:是否需要删除队尾元素。putVal等插入方法会在底部调用这个方法做检查。LinkedHashMap里面是直接return false,所以需要重写
return size() > maxSize;
}
public static void main(String[] args) {
LRUCache2 lru = new LRUCache2(5);
lru.put(1, "a");
lru.put(2, "b");
lru.put(3, "c");
lru.put(4, "d");
lru.put(5, "e");
System.out.println("原始链表为:" + lru.toString()); // 原始链表为:{1=a, 2=b, 3=c, 4=d, 5=e}
lru.get(4);
System.out.println("获取key为4的元素之后的链表:" + lru.toString()); // 获取key为4的元素之后的链表:{1=a, 2=b, 3=c, 5=e, 4=d}
lru.put(6, "f");
System.out.println("新添加一个key为6之后的链表:" + lru.toString()); // 新添加一个key为6之后的链表:{2=b, 3=c, 5=e, 4=d, 6=f}
lru.remove(3);
System.out.println("移除key=3的之后的链表:" + lru.toString()); // 移除key=3的之后的链表:{2=b, 5=e, 4=d, 6=f}
}
}
利用链表和hashmap实现:
新增:如果新数据项在链表中存在,则把该节点移到链表头部;如果不存在,则新建一个节点放到链表头部;若缓存满了,则把链表最后一个节点删除。
查找:如果数据项在链表中存在,则把该节点移到链表头部并返回对应节点,否则返回-1。
在链表尾部的节点就是最近最久未访问的数据项,链表头节点是最活跃的数据项。
import java.util.concurrent.ConcurrentHashMap;
public class LRUCacheMy<K, V> {
private int maxSize;
private ConcurrentHashMap<K, Node> map;
private Node first, last;
class Node {
K key;
V value;
Node pre, next;
Node(K k, V v) {
this.key = k;
this.value = v;
}
}
public LRUCacheMy(int size) {
this.maxSize = size;
map = new ConcurrentHashMap<>(size); //初始化map大小
}
public void put(K k, V v) {
Node n = map.get(k);
if (n != null) {
n.value = v;
} else {
n = new Node(k, v);
ensureCapacity();
}
// 把node挪到链表头、更新map
moveNodeToHead(n);
map.put(k, n);
}
public Object get(K k) {
Node n = map.get(k);
if (n == null) {
return null;
}
// 把node挪到链表头
moveNodeToHead(n);
return n.value;
}
public Object remove(K k) {
Node n = map.get(k);
if (n == null) {
return null;
}
// 删链表的元素、删map的元素
removeNode(n);
return map.remove(k);
}
/** 删除链表中的节点:改上下节点指向、判断首尾 */
private void removeNode(Node n) {
if (n.next != null) {
n.next.pre = n.pre;
}
if (n.pre != null) {
n.pre.next = n.next;
}
if (n == last) {
last = n.pre;
}
if (n == first) {
first = n.next;
}
n.pre = null;
n.next = null;
}
/** 把节点移到头部:先判首再判null和尾,再改上下节点指向、挂到头部 */
private void moveNodeToHead(Node n) {
if (n == first) {
return;
}
if (first == null || last == null) {
first = last = n;
return;
}
if (n == last) {
last = n.pre;
}
if (n.pre != null) {
n.pre.next = n.next;
}
if (n.next != null) {
n.next.pre = n.pre;
}
n.pre = null;
n.next = first;
first.pre = n;
first = n;
}
/** 确保容量够 */
private void ensureCapacity() {
if (map.size() < maxSize) {
return;
}
// 删队尾元素
if (last != null) {
// 必须先删map
map.remove(last.key);
// 在删链表
last = last.pre;
if (last != null) {
// 断开原last的关联
last.next.pre = null;
last.next = null;
} else {
first = null;
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node node = first;
while (node != null) {
sb.append(String.format("%s:%s ", node.key, node.value));
node = node.next;
}
return sb.toString();
}
public static void main(String[] args) {
LRUCacheMy<Integer, String> lru = new LRUCacheMy<Integer, String>(5);
lru.put(1, "a");
lru.put(2, "b");
lru.put(3, "c");
lru.put(4, "d");
lru.put(5, "e");
System.out.println("原始链表为:" + lru.toString()); // 原始链表为:5:e 4:d 3:c 2:b 1:a
lru.get(4);
System.out.println("获取key为4的元素之后的链表:" + lru.toString()); // 获取key为4的元素之后的链表:4:d 5:e 3:c 2:b 1:a
lru.put(6, "f");
System.out.println("新添加一个key为6之后的链表:" + lru.toString()); // 新添加一个key为6之后的链表:6:f 4:d 5:e 3:c 2:b
lru.remove(3);
System.out.println("移除key=3的之后的链表:" + lru.toString()); // 移除key=3的之后的链表:6:f 4:d 5:e 2:b
}
}