LRU和LFU都可以用作缓存置换算法
LRU
LRU是Least Recently Used的缩写,即最近最久未使用
主要有两种实现方式
继承LinkedHashMap
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int MAX_CACHE_SIZE;
public LRUCache(int cacheSize) {
// LinkedHashMap的一个构造函数,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面
// public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
// super(initialCapacity, loadFactor);
// this.accessOrder = accessOrder;
// }
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
MAX_CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_CACHE_SIZE;
}
}
只需重写removeEldestEntry方法,设置删除最老元素的条件即可以
双向链表加HashMap
public class LRUCache1<K, V> {
private final int MAX_CACHE_SIZE;
//链表头
private Entry first;
//链表尾,储存最旧的元素
private Entry last;
//键值对
private HashMap<K, Entry<K, V>> hashMap;
public LRUCache1(int cacheSize) {
MAX_CACHE_SIZE = cacheSize;
hashMap = new HashMap<K, Entry<K, V>>();
}
//基础方法put
public void put(K key, V value) {
Entry entry = getEntry(key);
if (entry == null) {
if (hashMap.size() >= MAX_CACHE_SIZE) {
//删除hashmap中的last元素
hashMap.remove(last.key);
//删除双向链表中的last节点
removeLast();
}
entry = new Entry();
entry.key = key;
}
entry.value = value;
//移到链表头部
moveToFirst(entry);
hashMap.put(key, entry);
}
//基础方法get
public V get(K key) {
Entry<K, V> entry = getEntry(key);
if (entry == null) return null;
moveToFirst(entry);
return entry.value;
}
//将entry移到头部
private void moveToFirst(Entry entry) {
if (entry == first) return;
if (entry.pre != null) entry.pre.next = entry.next;
if (entry.next != null) entry.next.pre = entry.pre;
if (entry == last) last = last.pre;
if (first == null || last == null) {
first = last = entry;
return;
}
entry.next = first;
first.pre = entry;
first = entry;
entry.pre = null;
}
//删除最老的元素
private void removeLast() {
if (last != null) {
last = last.pre;
if (last == null) first = null;
else last.next = null;
}
}
private Entry<K, V> getEntry(K key) {
return hashMap.get(key);
}
//双向链表
class Entry<K, V> {
public Entry pre;
public Entry next;
public K key;
public V value;
}
}
LFU
LFU是Least Frequently Used的缩写,使用频率最少的
两个HashMap加双向链表嵌套LinkedHashSet
public class LFUCache2<K,V> {
//链表头部
private Node head = null;
private int cap = 0;
//存储数据的
private HashMap<K, V> valueHash = null;
//key所属Node
private HashMap<K, Node> nodeHash = null;
public LFUCache2(int capacity) {
this.cap = capacity;
valueHash = new HashMap<K, V>();
nodeHash = new HashMap<K, Node>();
}
//基础方法get
public Object get(K key) {
if (valueHash.containsKey(key)) {
increaseCount(key);
return valueHash.get(key);
}
return -1;
}
//基础方法set
public void set(K key, V value) {
if ( cap == 0 ) return;
//如果存在当前key则覆盖,并刷新key的次序
if (valueHash.containsKey(key)) {
valueHash.put(key, value);
Node node = nodeHash.get(key);
node.keys.remove(key);
node.keys.add(key);
} else {
if (valueHash.size() < cap) {
valueHash.put(key, value);
} else {
//如果超出容量则要移除最旧的
removeOld();
valueHash.put(key, value);
}
//因为次数为1那肯定是添加的headNode
addToHead(key);
}
increaseCount(key);
}
//新添加值时添加到haed
private void addToHead(K key) {
//添加第一个值时会进入第一个if,其他情况head均不为空且次数不为0
if (head == null) {
head = new Node(0);
head.keys.add(key);
} else if (head.count > 0) {
Node node = new Node(0);
node.keys.add(key);
node.next = head;
head.prev = node;
head = node;
} else {
//按道理不会走进这个else,因为插入第一个值后head均不为空且次数不为0
head.keys.add(key);
}
nodeHash.put(key, head);
}
//增加次数
private void increaseCount(K key) {
//根据key获得所属Node
Node node = nodeHash.get(key);
//因为次数要加一,所以要从Node的LinkHashSet移除掉
node.keys.remove(key);
//没有key次数比当前Node大的Node则创建一个,并在添加到新Node的LinkHashSet
if (node.next == null) {
node.next = new Node(node.count+1);
node.next.prev = node;
node.next.keys.add(key);
} else if (node.next.count == node.count+1) {
node.next.keys.add(key);
} else {
//如果下一个Node的次数不是当前key次数+1,则新建一个并插入到node后面
Node tmp = new Node(node.count+1);
tmp.keys.add(key);
tmp.prev = node;
tmp.next = node.next;
node.next.prev = tmp;
node.next = tmp;
}
//覆盖key所属Node
nodeHash.put(key, node.next);
//如果没有key的次数和修改后的Node相等则移除当前Node
if (node.keys.size() == 0) remove(node);
}
//删除最旧的元素
private void removeOld() {
if (head == null) return;
Object old = null;
//获取haed的第一个元素
for (Object n: head.keys) {
old = n;
break;
}
//从haed的中的LinkHashSet移除掉
head.keys.remove(old);
//如果不存在当前次数的key则把head也移除掉
if (head.keys.size() == 0) remove(head);
//移除掉key的node的对应关系
nodeHash.remove(old);
//键值对移除掉当前key
valueHash.remove(old);
}
//双向链表的删除
private void remove(Node node) {
if (node.prev == null) {
head = node.next;
} else {
node.prev.next = node.next;
}
if (node.next != null) {
node.next.prev = node.prev;
}
}
//双向链表节点,装载次数和对应次数的keys的LinkedHashSet
class Node {
public int count = 0;
public LinkedHashSet<K> keys = null;
public Node prev = null, next = null;
public Node(int count) {
this.count = count;
keys = new LinkedHashSet<K>();
prev = next = null;
}
}
}