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

java实现时间复杂度O(1)的LFU缓存_第1张图片

操作:

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

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


               








你可能感兴趣的:(java基础)