java實現時間複雜度O(1)的LFU快取
阿新 • • 發佈:2019-01-31
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(); } }
結果 命中數1:1,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, 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,