LFU缓存一般需要排序来解决命中率问题(上一篇的LFU实现也是利用了Collections.sort),导致时间复杂度较高。下面采用一种算法让LFU的时间复杂度成为O(1)。
数据设计:
1,一个双向链表来保存命中数(下面代码的NodeCount<K> countHead,结构中包含2的map)。
2,命中数相同的放在一个双向链表的map中(这里用的是LinkedHashMap,主要是利用了它的accessOrder属性来实现根据访问顺序来排序);
3,一个集合来保存所有的元素(这里用的是HashMap,因为只要key的hash算法合理的理想情况下,put,get操作是O(1)。为了避免遍历,HashMap的value包含了1的node【下面代码的ValueObject<K,V>】);
操作:
超过缓存大小的删除策略:
- 把频率节点1下的数据删除掉,不够就后移到2.。。。
- 把hash表里的对应节点都删除掉
get操作
- 根据一个key,到全局hash表里获取这个数据节点,比如说是y
- 由于y被多访问了一次,此时其访问频率增加了1,于是要进行位置更替
- 访问前,y的访问频率是1,访问后变成了2 。
- 找到y对应的频率节点 1,看看其next指针。如果指向为空,则创建一个新的频率节点 2,把y移到频率节点2下,同时删除频率节点1下的那个。如果指向不为空,看看其指向频率节点的值是否为2,如果是,则直接移动。如果不是,则要创建一个频率节点2,然后再移动
package chin.tei.lfu;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
public class CacheLFU<K,V> {
private static final Object PRESENT = new Object();
final int maxCapacity;
final HashMap<K,ValueObject<K,V>> cacheMap;
NodeCount<K> countHead;
public CacheLFU(int maxCapacity) throws Exception {
if (maxCapacity < 1) {
throw new Exception("请设置正确的最大容量");
}
this.maxCapacity = maxCapacity;
cacheMap = new HashMap<K,ValueObject<K,V>>(maxCapacity);
}
private static class NodeCount<K> {
int count;
NodeCount<K> next;
NodeCount<K> prev;
LinkedHashMap<K,Object> linkMap;
NodeCount(NodeCount<K> prev, int count, NodeCount<K> next) {
this.count = count;
this.next = next;
this.prev = prev;
}
}
private static class ValueObject<K,V> {
V value;
NodeCount<K> node;
ValueObject(V val, NodeCount<K> 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<K> 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<K> 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<K> be = null;
// 新加入缓存的场合
if (!cacheMap.containsKey(key)) {
LinkedHashMap<K,Object> newLinkMap = new LinkedHashMap<K,Object>(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<K> first = countHead;
NodeCount<K> nodeCount = new NodeCount<K>(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<K,V>(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<K> currentNode = cacheMap.get(key).node;
currentNode.linkMap.remove(key);
int currentCount = currentNode.count;
int nextCount = currentCount + 1;
LinkedHashMap<K,Object> newLinkMap = new LinkedHashMap<K,Object>(maxCapacity, 0.75f, true);
NodeCount<K> after = currentNode.next;
NodeCount<K> before = currentNode.prev;
if (currentNode.linkMap.size() == 0) {
currentNode = null;
} else {
before = currentNode;
}
// 下一个节点没有的场合,新增一个+1的节点放到最后
if (after == null) {
NodeCount<K> nodeCount = new NodeCount<K>(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<K> nodeCount = new NodeCount<K>(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<K> node = countHead;
Iterator<K> 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<String, String> cache = new CacheLFU<String, String>(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());
}
}
结果
命中数1:1,
命中数1:1, 2,
命中数1:2, 命中数2:1,
命中数1:2, 3, 命中数2:1,
命中数1:2, 3, 命中数3:1,
命中数1:3, 命中数2:2, 命中数3:1,
命中数1:3, 4, 命中数2:2, 命中数3:1,
命中数1:3, 4, 命中数2:2, 命中数4:1,
命中数1:3, 4, 命中数3:2, 命中数4:1,
命中数1:4, 命中数2:3, 命中数3:2, 命中数4:1,
命中数1:4, 5, 命中数2:3, 命中数3:2, 命中数4:1,
命中数1:5, 6, 命中数2:3, 命中数3:2, 命中数4:1,
命中数1:6, 7, 命中数2:3, 命中数3:2, 命中数4:1,
命中数1:6, 命中数2:3, 7, 命中数3:2, 命中数4:1,
命中数1:1, 2,
命中数1:2, 命中数2:1,
命中数1:2, 3, 命中数2:1,
命中数1:2, 3, 命中数3:1,
命中数1:3, 命中数2:2, 命中数3:1,
命中数1:3, 4, 命中数2:2, 命中数3:1,
命中数1:3, 4, 命中数2:2, 命中数4:1,
命中数1:3, 4, 命中数3:2, 命中数4:1,
命中数1:4, 命中数2:3, 命中数3:2, 命中数4:1,
命中数1:4, 5, 命中数2:3, 命中数3:2, 命中数4:1,
命中数1:5, 6, 命中数2:3, 命中数3:2, 命中数4:1,
命中数1:6, 7, 命中数2:3, 命中数3:2, 命中数4:1,
命中数1:6, 命中数2:3, 7, 命中数3:2, 命中数4:1,