LFU缓存一般需要排序来解决命中率问题(上一篇的LFU实现也是利用了Collections.sort),导致时间复杂度较高。下面采用一种算法让LFU的时间复杂度成为O(1)。
数据设计:
1,一个双向链表来保存命中数(下面代码的NodeCount
2,命中数相同的放在一个双向链表的map中(这里用的是LinkedHashMap,主要是利用了它的accessOrder属性来实现根据访问顺序来排序);
3,一个集合来保存所有的元素(这里用的是HashMap,因为只要key的hash算法合理的理想情况下,put,get操作是O(1)。为了避免遍历,HashMap的value包含了1的node【下面代码的ValueObject
操作:
超过缓存大小的删除策略:
get操作
package chin.tei.lfu;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
public class CacheLFU {
private static final Object PRESENT = new Object();
final int maxCapacity;
final HashMap> cacheMap;
NodeCount countHead;
public CacheLFU(int maxCapacity) throws Exception {
if (maxCapacity < 1) {
throw new Exception("请设置正确的最大容量");
}
this.maxCapacity = maxCapacity;
cacheMap = new HashMap>(maxCapacity);
}
private static class NodeCount {
int count;
NodeCount next;
NodeCount prev;
LinkedHashMap linkMap;
NodeCount(NodeCount prev, int count, NodeCount next) {
this.count = count;
this.next = next;
this.prev = prev;
}
}
private static class ValueObject {
V value;
NodeCount node;
ValueObject(V val, NodeCount node) {
this.value = val;
this.node = node;
}
}
// 放入缓存
public void put(K key, V value) throws Exception {
// 容量不足时缓存删除
removeCache(key);
// 放入缓存
putVal(key, value);
}
// 缓存删除
@SuppressWarnings("unchecked")
private void removeCache(K key) throws Exception {
if (cacheMap.containsKey(key)) {
return;
}
NodeCount first;
K removeKey = null;
// 超过最大缓存容量
while(cacheMap.size() >= maxCapacity ) {
// 第一个节点
if ((first=countHead) != null) {
// 节点元素存在
if (first.linkMap != null && !first.linkMap.isEmpty()) {
// 该节点只有一个元素的场合
if (first.linkMap.size() == 1) {
removeKey = (K) first.linkMap.keySet().toArray()[0];
countHead = (first.next == null ? null : first.next);
countHead.prev = null;
first = null;
} else {
Iterator iterator = first.linkMap.keySet().iterator();
if (iterator.hasNext()) {
removeKey = iterator.next();
first.linkMap.remove(removeKey);
}
}
cacheMap.remove(removeKey);
// 节点元素不存在
} else {
countHead = first.next;
}
}
}
}
// 放入缓存
private void putVal(K key, V val) {
NodeCount be = null;
// 新加入缓存的场合
if (!cacheMap.containsKey(key)) {
LinkedHashMap newLinkMap = new LinkedHashMap(maxCapacity, 0.75f, true);
// 有缓存一次的场合
if (countHead != null && countHead.count == 1){
if (countHead.linkMap == null) {
countHead.linkMap = newLinkMap;
}
countHead.linkMap.put(key,PRESENT);
be = countHead;
} else {
NodeCount first = countHead;
NodeCount nodeCount = new NodeCount(null, 1, countHead == null ? null : first);
newLinkMap.put(key,PRESENT);
nodeCount.linkMap = newLinkMap;
be = nodeCount;
// 缓存不为空,即存在大于1的缓存,把1放在前面
if (countHead != null) {
first.prev = nodeCount;
}
countHead = nodeCount;
}
} else {
moveCount(key);
}
cacheMap.put(key, new ValueObject(val, be));
}
// 从缓存中取得数据,同时随着访问次数的增加,移动元素
public V get(K key) {
if (!cacheMap.containsKey(key)) {
return null;
}
moveCount(key);
return cacheMap.get(key).value;
}
// 随着访问次数增加来移动元素
private void moveCount(K key) {
NodeCount currentNode = cacheMap.get(key).node;
currentNode.linkMap.remove(key);
int currentCount = currentNode.count;
int nextCount = currentCount + 1;
LinkedHashMap newLinkMap = new LinkedHashMap(maxCapacity, 0.75f, true);
NodeCount after = currentNode.next;
NodeCount before = currentNode.prev;
if (currentNode.linkMap.size() == 0) {
currentNode = null;
} else {
before = currentNode;
}
// 下一个节点没有的场合,新增一个+1的节点放到最后
if (after == null) {
NodeCount nodeCount = new NodeCount(before, nextCount, null);
newLinkMap.put(key, PRESENT);
nodeCount.linkMap = newLinkMap;
cacheMap.get(key).node = nodeCount;
before.next = nodeCount;
// 下一个正好是+1次数的节点,直接追加
} else if (after.count == nextCount) {
after.linkMap.put(key, PRESENT);
before.next = after;
after.prev = before;
cacheMap.get(key).node = after;
// 下一个节点的次数>+1次数,新建+1节点,再连接前后节点
} else if (after.count > nextCount) {
NodeCount nodeCount = new NodeCount(before, nextCount, after);
newLinkMap.put(key, PRESENT);
nodeCount.linkMap = newLinkMap;
cacheMap.get(key).node = nodeCount;
before.next = nodeCount;
after.prev = nodeCount;
}
}
public String toString() {
StringBuilder returnString = new StringBuilder();
NodeCount node = countHead;
Iterator iterator = null;
while(node != null) {
returnString.append("命中数" + node.count + ":");
iterator = node.linkMap.keySet().iterator();
while (iterator.hasNext()) {
returnString.append(iterator.next() + ", ");
}
node = node.next;
}
return returnString.toString();
}
}
package chin.tei.lfu;
public class TestCacheLFU {
public static void main(String[] args) throws Exception {
// TODO 自动生成的方法存根
CacheLFU cache = new CacheLFU(5);
cache.put("1","1");
System.out.println(cache.toString());
cache.put("2","2");
System.out.println(cache.toString());
cache.get("1");
System.out.println(cache.toString());
cache.put("3","3");
System.out.println(cache.toString());
cache.get("1");
System.out.println(cache.toString());
cache.get("2");
System.out.println(cache.toString());
cache.put("4","4");
System.out.println(cache.toString());
cache.get("1");
System.out.println(cache.toString());
cache.get("2");
System.out.println(cache.toString());
cache.get("3");
System.out.println(cache.toString());
cache.put("5","5");
System.out.println(cache.toString());
cache.put("6","6");
System.out.println(cache.toString());
cache.put("7","7");
System.out.println(cache.toString());
cache.put("7","77");
System.out.println(cache.toString());
}
}
结果