java lfu缓存_java实现时间复杂度O(1)的LFU缓存

LFU缓存一般需要排序来解决命中率问题(上一篇的LFU实现也是利用了Collections.sort),导致时间复杂度较高。下面采用一种算法让LFU的时间复杂度成为O(1)。

数据设计:

1,一个双向链表来保存命中数(下面代码的NodeCount countHead,结构中包含2的map)。

2,命中数相同的放在一个双向链表的map中(这里用的是LinkedHashMap,主要是利用了它的accessOrder属性来实现根据访问顺序来排序);

3,一个集合来保存所有的元素(这里用的是HashMap,因为只要key的hash算法合理的理想情况下,put,get操作是O(1)。为了避免遍历,HashMap的value包含了1的node【下面代码的ValueObject】);

88a0d439d45d6c7cd13ec3023ade6b82.png

操作:

超过缓存大小的删除策略:

把频率节点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 {

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());

}

}结果

命中数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,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值