LRU 最近最久未使用,是一种常用的淘汰算法,广泛应用与OS内存页面调度,Redis缓存淘汰策略,等等。在Java中有两种实现方式,一个是直接利用JDK已有的 LInkedHashMap ,一个是自己手写 DoubleLinkedList 结合HashMap。
一:利用JDK方法
public class LRU<K,V> extends LinkedHashMap<K,V>{
// 容量
private Integer maxEntries;
// 超过容量则删除最久未使用的,注意是覆盖父类的方法
@Override
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > maxEntries;
}
// 这里的accessOrder要设置成true
public LRU(int initialCapacity, float loadFactor, boolean accessOrder, Integer maxEntries) {
super(initialCapacity, loadFactor, true);
this.maxEntries = maxEntries;
}
}
二:自己实现上面的数据结构
心里很清楚是HashMap中的节点又用指针串成双端链表,但是面试的时候在半小时内正确地写出来,还是有很大挑战的。有泛型,静态内部类,链表删除与替换,考察点还挺多的
public class MyLRU<K,V> {
private Map<K,Node<K,V>> map = new HashMap<>();
private MyLinkedList<K,V> list = new MyLinkedList<>();
// 和上面的容量一个意思
private int capacity;
public MyLRU(int capacity) {
this.capacity = capacity;
}
//往LRU中添加数据,主要用map的put方法
public void add(K key, V value){
if(map.containsKey(key)){
Node<K,V> node = map.get(key);
node.value = value;
map.put(key,node);
list.moveToTail(node);
}else{
if(map.size() == capacity){
K k = list.removeHead();
map.remove(k);
}
Node<K,V> node = new Node<>(key, value);
map.put(key,node);
list.add(node);
}
}
// 从LRU中获取数据,要把数据提取到最近使用
public V get(K key){
if(map.containsKey(key)){
Node<K,V> node = map.get(key);
list.moveToTail(node);
return node.value;
}
return null;
}
private static class Node<K, V> {
K key;
V value;
Node<K,V> prev;
Node<K,V> next;
Node(K key, V value) {
this.key = key;
this.value = value;
}
}
private static class MyLinkedList<K,V> {
// head 是老数据
Node<K,V> head;
Node<K,V> tail;
// 添加新数据到tail
private void add(Node<K,V> node) {
if (node == null) {
throw new NullPointerException("node 为 空");
}
if (head == null) {
head = node;
tail = node;
} else {
tail.next = node;
node.prev = tail;
tail = node;
}
}
// 移动数据到tail
private void moveToTail(Node<K,V> node) {
if (node == null) {
throw new NullPointerException("node 为 空");
}
// 已经末尾了
if (node.next == null) {
return;
}
if (node.prev == null) {
head = head.next;
}
node.next.prev = node.prev;
if (node.prev != null) {
node.prev.next = node.next;
}
tail.next = node;
node.prev = tail;
node.next = null;
tail = node;
}
// 删除最老的数据
private K removeHead() {
if(head == null) {
return null;
}
K key = head.key;
if(head == tail){
head = null;
tail = null;
}else{
head = head.next;
head.prev = null;
}
return key;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node cur = head;
while(cur != null){
sb.append(cur.key).append(" ");
cur = cur.next;
}
sb.append("\n头是 ").append(head.key).append(" 尾是 ").append(tail.key);
return sb.toString();
}
}
public static void main(String[] args) {
MyLRU<String,Integer> myLRU = new MyLRU<>(3);
myLRU.add("A", 1);
myLRU.add("B", 2);
myLRU.add("C", 3);
System.out.println(myLRU.get("A"));
myLRU.add("D", 4);
System.out.println(myLRU.get("B"));
System.out.println(myLRU.list);
}
}