先進先出,若是緩存容量滿,則優先移出最先加入緩存的數據;其內部可使用隊列實現。
(想自學習編程的小夥伴請搜索圈T社區,更多行業相關資訊更有行業相關免費視頻教程。徹底免費哦!)面試
1)Object get(key):獲取保存的數據,若是數據不存在或者已通過期,則返回null。算法
2)void put(key,value,expireTime):加入緩存,不管此key是否已存在,均做爲新key處理(移除舊key);若是空間不足,則移除已過時的key,若是沒有,則移除最先加入緩存的key。過時時間未指定,則表示永不自動過時。編程
3)此題須要注意,咱們容許key是有過時時間的,這一點與普通的FIFO有所區別,因此在設計此題時須要注意。(也是面試考察點,此題偏設計而非算法)緩存
普通的FIFO或許你們都能很簡單的寫出,此處增長了過時時間的特性,因此在設計時須要多考慮。以下示例,爲FIFO的簡易設計,還沒有考慮併發環境場景。數據結構
1)用普通的hashMap保存緩存數據。併發
2)咱們須要額外的map用來保存key的過時特性,例子中使用了TreeMap,將「剩餘存活時間」做爲key,利用treemap的排序特性。學習
public class FIFOCache { //按照訪問時間排序,保存全部key-value private final Map<String,Value> CACHE = new LinkedHashMap<>(); //過時數據,只保存有過時時間的key //暫不考慮併發,咱們認爲同一個時間內沒有重複的key,若是改造的話,能夠將value換成set private final TreeMap<Long, String> EXPIRED = new TreeMap<>(); private final int capacity; public FIFOCache(int capacity) { this.capacity = capacity; } public Object get(String key) { // Value value = CACHE.get(key); if (value == null) { return null; } //若是不包含過時時間 long expired = value.expired; long now = System.nanoTime(); //已過時 if (expired > 0 && expired <= now) { CACHE.remove(key); EXPIRED.remove(expired); return null; } return value.value; } public void put(String key,Object value) { put(key,value,-1); } public void put(String key,Object value,int seconds) { //若是容量不足,移除過時數據 if (capacity < CACHE.size()) { long now = System.nanoTime(); //有過時的,所有移除 Iterator<Long> iterator = EXPIRED.keySet().iterator(); while (iterator.hasNext()) { long _key = iterator.next(); //若是已過時,或者容量仍然溢出,則刪除 if (_key > now) { break; } //一次移除全部過時key String _value = EXPIRED.get(_key); CACHE.remove(_value); iterator.remove(); } } //若是仍然容量不足,則移除最先訪問的數據 if (capacity < CACHE.size()) { Iterator<String> iterator = CACHE.keySet().iterator(); while (iterator.hasNext() && capacity < CACHE.size()) { String _key = iterator.next(); Value _value = CACHE.get(_key); long expired = _value.expired; if (expired > 0) { EXPIRED.remove(expired); } iterator.remove(); } } //若是此key已存在,移除舊數據 Value current = CACHE.remove(key); if (current != null && current.expired > 0) { EXPIRED.remove(current.expired); } //若是指定了過時時間 if(seconds > 0) { long expireTime = expiredTime(seconds); EXPIRED.put(expireTime,key); CACHE.put(key,new Value(expireTime,value)); } else { CACHE.put(key,new Value(-1,value)); } } private long expiredTime(int expired) { return System.nanoTime() + TimeUnit.SECONDS.toNanos(expired); } public void remove(String key) { Value value = CACHE.remove(key); if(value == null) { return; } long expired = value.expired; if (expired > 0) { EXPIRED.remove(expired); } } class Value { long expired; //過時時間,納秒 Object value; Value(long expired,Object value) { this.expired = expired; this.value = value; } } }
least recently used,最近最少使用,是目前最經常使用的緩存算法和設計方案之一,其移除策略爲「當緩存(頁)滿時,優先移除最近最久未使用的數據」,優勢是易於設計和使用,適用場景普遍。算法能夠參考leetcode 146 (LRU Cache)。
1)Object get(key):從canche中獲取key對應的數據,若是此key已過時,移除此key,並則返回null。this
2)void put(key,value,expired):設置k-v,若是容量不足,則根據LRU置換算法移除「最久未被使用的key」,須要注意,根據LRU優先移除已過時的keys,若是沒有,則根據LRU移除未過時的key。若是未設定過時時間,則認爲永不自動過時。設計
3)此題,設計關鍵是過時時間特性,這與常規的LRU有所不一樣。畢竟「過時時間」特性在cache設計中是必要的。code
1)LRU的基礎算法,須要瞭解;每次put、get時須要更新key對應的訪問時間,咱們須要一個數據結構可以保存key最近的訪問時間且可以排序。
2)既然包含過時時間特性,那麼帶有過時時間的key須要額外的數據結構保存。
3)暫時不考慮併發操做;儘可能兼顧空間複雜度和時間複雜度。
4)此題仍然偏向於設計題,而非純粹的算法題。
此題代碼與FIFO基本相同,惟一不一樣點爲get()方法,對於LRU而言,get方法須要重設訪問時間(即調整所在cache中順序)
public Object get(String key) { // Value value = CACHE.get(key); if (value == null) { return null; } //若是不包含過時時間 long expired = value.expired; long now = System.nanoTime(); //已過時 if (expired > 0 && expired <= now) { CACHE.remove(key); EXPIRED.remove(expired); return null; } //相對於FIFO,增長順序重置 CACHE.remove(key); CACHE.put(key,value); return value.value; } 複製代碼LFU 最近最不經常使用,當緩存容量滿時,移除訪問次數最少的元素,若是訪問次數相同的元素有多個,則移除最久訪問的那個。設計要求參見leetcode 460( LFU Cache) public class LFUCache { //主要容器,用於保存k-v private Map<String, Object> keyToValue = new HashMap<>(); //記錄每一個k被訪問的次數 private Map<String, Integer> keyToCount = new HashMap<>(); //訪問相同次數的key列表,按照訪問次數排序,value爲相同訪問次數到key列表。 private TreeMap<Integer, LinkedHashSet<String>> countToLRUKeys = new TreeMap<>(); private int capacity; public LFUCache(int capacity) { this.capacity = capacity; //初始化,默認訪問1次,主要是解決下文 } public Object get(String key) { if (!keyToValue.containsKey(key)) { return null; } touch(key); return keyToValue.get(key); } /** * 若是一個key被訪問,應該將其訪問次數調整。 * @param key */ private void touch(String key) { int count = keyToCount.get(key); keyToCount.put(key, count + 1);//訪問次數增長 //從原有訪問次數統計列表中移除 countToLRUKeys.get(count).remove(key); //若是符合最少調用次數到key統計列表爲空,則移除此調用次數到統計 if (countToLRUKeys.get(count).size() == 0) { countToLRUKeys.remove(count); } //而後將此key的統計信息加入到管理列表中 LinkedHashSet<String> countKeys = countToLRUKeys.get(count + 1); if (countKeys == null) { countKeys = new LinkedHashSet<>(); countToLRUKeys.put(count + 1,countKeys); } countKeys.add(key); } public void put(String key, Object value) { if (capacity <= 0) { return; } if (keyToValue.containsKey(key)) { keyToValue.put(key, value); touch(key); return; } //容量超額以後,移除訪問次數最少的元素 if (keyToValue.size() >= capacity) { Map.Entry<Integer,LinkedHashSet<String>> entry = countToLRUKeys.firstEntry(); Iterator<String> it = entry.getValue().iterator(); String evictKey = it.next(); it.remove(); if (!it.hasNext()) { countToLRUKeys.remove(entry.getKey()); } keyToCount.remove(evictKey); keyToValue.remove(evictKey); } keyToValue.put(key, value); keyToCount.put(key, 1); LinkedHashSet<String> keys = countToLRUKeys.get(1); if (keys == null) { keys = new LinkedHashSet<>(); countToLRUKeys.put(1,keys); } keys.add(key); } }