1. 程式人生 > >java實現時間複雜度O(1)的LFU快取

java實現時間複雜度O(1)的LFU快取

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,