Java實現緩存(LRU,FIFO)
吹吹牛逼,曬曬太陽。不如來寫點東西,哈哈哈哈哈。。。。今天來說說,如何用java實現緩存,這個話題很多面試的也會被問到。今天就來說說。
1.為什麽要java實現緩存的?
由於目前軟件或網頁的並發量增加很大,大量請求直接操作數據庫,會對數據造成很大的壓力。處理大量請求和連接時間會很長。而我們知道數據庫中70%的數據是不需要修改的,那就可以引入緩存來進行讀取,減少數據庫的壓力。
常用的緩存有Redis和memcached,但是有時候一些小場景就可以直接使用Java實現緩存,就可以滿足這部分服務的需求。
緩存主要有LRU和FIFO,LRU是Least Recently Used的縮寫,即最近最久未使用,FIFO就是先進先出,
下面就使用Java來實現這兩種緩存
一。LRU緩存的思想
固定緩存大小,需要給緩存分配一個固定的 大小
每次讀取緩存都會改變緩存的使用時間,將緩存存在的時間重新刷新。
需要在緩存滿後,將最近,最久,未使用的緩存刪除,再添加心得緩存。
按照上面的思想我們可以使用LikedHashMap來實現LRU緩存
當返回true的時候,就會remove其中最久的元素,可以通過重寫這個方法來控制緩存元素的刪除,當緩存滿了後,就可以通過返回true刪除最久未被使用的元素,達到LRU的要求。這樣就可以滿足上述第三點要求。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
由於LinkedHashMap是為自動擴容的,當table數組中元素大於Capacity * loadFactor的時候,就會自動進行兩倍擴容。但是為了使緩存大小固定,就需要在初始化的時候傳入容量大小和負載因子。
為了使得到達設置緩存大小不會進行自動擴容,需要將初始化的大小進行計算再傳入,可以將初始化大小設置為(緩存大小 / loadFactor) + 1,這樣就可以在元素數目達到緩存大小時,也不會進行擴容了。這樣就解決了上述第一點問題。
代碼實現
package com.huojg.test.Test; import java.util.LinkedHashMap; import java.util.Map;import java.util.Set; public class LRUCache<K, V> { private final int MAX_CACHE_SIZE; private final float DEFAULT_LOAD_FACTORY = 0.75f; LinkedHashMap<K, V> map; public LRUCache(int cacheSize) { MAX_CACHE_SIZE = cacheSize; int capacity = (int)Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTORY) + 1; /* * 第三個參數設置為true,代表linkedlist按訪問順序排序,可作為LRU緩存 * 第三個參數設置為false,代表按插入順序排序,可作為FIFO緩存 */ map = new LinkedHashMap<K, V>(capacity, DEFAULT_LOAD_FACTORY, true) { @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > MAX_CACHE_SIZE; } }; } public synchronized void put(K key, V value) { map.put(key, value); } public synchronized V get(K key) { return map.get(key); } public synchronized void remove(K key) { map.remove(key); } public synchronized Set<Map.Entry<K, V>> getAll() { return map.entrySet(); } @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); for (Map.Entry<K, V> entry : map.entrySet()) { stringBuilder.append(String.format("%s: %s ", entry.getKey(), entry.getValue())); } return stringBuilder.toString(); } public static void main(String[] args) { LRUCache<Integer, Integer> lru1 = new LRUCache<>(5); lru1.put(1, 1); lru1.put(2, 2); lru1.put(3, 3); System.out.println(lru1); lru1.get(1); System.out.println(lru1); lru1.put(4, 4); lru1.put(5, 5); lru1.put(6, 6); System.out.println(lru1); } }
結果輸出:
1: 1 2: 2 3: 3 2: 2 3: 3 1: 1 3: 3 1: 1 4: 4 5: 5 6: 6
實現了LRU緩存的思想
FIFO
FIFO就是先進先出,可以使用LinkedHashMap進行實現。
當第三個參數傳入為false或者是默認的時候,就可以實現按插入順序排序,就可以實現FIFO緩存了。
實現代碼跟上述使用LinkedHashMap實現LRU的代碼基本一致,主要就是構造函數的傳值有些不同。
package com.huojg.test.Test; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; public class FIFOCache<K, V> { private final int MAX_CACHE_SIZE; private final float DEFAULT_LOAD_FACTORY = 0.75f; LinkedHashMap<K, V> map; public FIFOCache(int cacheSize) { MAX_CACHE_SIZE = cacheSize; int capacity = (int)Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTORY) + 1; /* * 第三個參數設置為true,代表linkedlist按訪問順序排序,可作為LRU緩存 * 第三個參數設置為false,代表按插入順序排序,可作為FIFO緩存 */ map = new LinkedHashMap<K, V>(capacity, DEFAULT_LOAD_FACTORY, false) { @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > MAX_CACHE_SIZE; } }; } public synchronized void put(K key, V value) { map.put(key, value); } public synchronized V get(K key) { return map.get(key); } public synchronized void remove(K key) { map.remove(key); } public synchronized Set<Map.Entry<K, V>> getAll() { return map.entrySet(); } @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); for (Map.Entry<K, V> entry : map.entrySet()) { stringBuilder.append(String.format("%s: %s ", entry.getKey(), entry.getValue())); } return stringBuilder.toString(); } public static void main(String[] args) { FIFOCache<Integer, Integer> lru1 = new FIFOCache<>(5); lru1.put(1, 1); lru1.put(2, 2); lru1.put(3, 3); System.out.println(lru1); lru1.get(1); System.out.println(lru1); lru1.put(4, 4); lru1.put(5, 5); lru1.put(6, 6); System.out.println(lru1); } }
結果輸出
1: 1 2: 2 3: 3 1: 1 2: 2 3: 3 2: 2 3: 3 4: 4 5: 5 6: 6
以上就是使用Java實現這兩種緩存的方式,從中可以看出,LinkedHashMap實現緩存較為容易,因為底層函數對此已經有了支持,自己編寫鏈表實現LRU緩存也是借鑒了LinkedHashMap中實現的思想。在Java中不只是這兩種數據結構可以實現緩存,比如ConcurrentHashMap、WeakHashMap在某些場景下也是可以作為緩存的,到底用哪一種數據結構主要是看場景再進行選擇,但是很多思想都是可以通用的。
Java實現緩存(LRU,FIFO)