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

来源:互联网 发布:tensorflow wordvec 编辑:程序博客网 时间:2024/05/29 15:28

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,    


               








阅读全文
0 0
原创粉丝点击