LRU、LFU演算法java實現
近期使用springboot整合ehcache實現快取,spring還支援使用簡單ConcurrentMapCache實現,底層就是用ConcurrentHashMap實現。ehcache相對來說比較重,加pom依賴下載了很長時間,但是ehcache有很多可配置的選項,其中包括快取達到一定大小淘汰的演算法的選擇。包括了FIFO、LRU、LFU可以根據不同的業務場景選擇。
一、LRU的實現比較簡單,因為java中的LinkedHashMap有很多特點正好適合LRU的思想。LRU(least recently used)最近最少使用,首先淘汰最長時間未被使用的頁面。使用連結串列實現,當一個元素被訪問了,把元素移動到連結串列的頂端,插入新元素也放到頂端,當快取數量到達限制,直接從連結串列底端移除元素。
public class LRU<k, v> extends LinkedHashMap<k, v> {
private final int MAX_SIZE;
public LRU(int capcity) {
super(8, 0.75f,true);
this.MAX_SIZE = capcity;
}
@Override
public boolean removeEldestEntry(Map.Entry<k, v> eldest) {
if (size() > MAX_SIZE) {
System.out .println("移除的元素為:" + eldest.getValue());
}
return size() > MAX_SIZE;
}
public static void main(String[] args) {
Map<Integer, Integer> map = new LRU<>(5);
for (int i = 1; i <= 11; i++) {
map.put(i, i);
System.out.println("cache的容量為:" + map.size());
if (i == 4) {
map.get(1);
}
}
System.out.println("=-=-=-=-=-=-=-map元素:");
map.entrySet().forEach(integerIntegerEntry -> System.out.println(integerIntegerEntry.getValue()));
}
}
LinkedHashMap有一個accessOrder的引數正好和LRU的思路相契合,這裡使用0.75的預設載入因子(載入因子過小空間利用率低,衝突減少,訪問速度快,載入因子過大,反之。載入因子過大,當執行put操作,兩個物件的hashcode相同時要操作連結串列,相應的get操作是也要操作連結串列,這樣就使得訪問變慢。)
removeEldestEntry方法當結果返回為true時,它會清除map中的最老元素。以實現LRU的演算法。
執行結果為:
cache的容量為:1
cache的容量為:2
cache的容量為:3
cache的容量為:4
cache的容量為:5
移除的元素為:2
cache的容量為:5
移除的元素為:3
cache的容量為:5
移除的元素為:4
cache的容量為:5
移除的元素為:1
cache的容量為:5
移除的元素為:5
cache的容量為:5
移除的元素為:6
cache的容量為:5
=-=-=-=-=-=-=-map元素:
7
8
9
10
11
程式碼裡面為了測試,在加入元素4的時候,訪問了一下元素1,然後看到,在快取達到限制時,最先移除的不是1,而是2,3然後是1。
二、LFU(Least Frequently Used)淘汰一定時期內被訪問次數最少的元素。如果元素的一定時間內的訪問次數相同時,則比較他們的最新一次的訪問時間。
public class LFU<k, v> {
private final int capcity;
private Map<k, v> cache = new HashMap<>();
private Map<k, HitRate> count = new HashMap<>();
public LFU(int capcity) {
this.capcity = capcity;
}
public void put(k key, v value) {
v v = cache.get(key);
if (v == null) {
if (cache.size() == capcity) {
removeElement();
}
count.put(key, new HitRate(key, 1, System.nanoTime()));
} else {
addHitCount(key);
}
cache.put(key, value);
}
public v get(k key) {
v value = cache.get(key);
if (value != null) {
addHitCount(key);
return value;
}
return null;
}
//移除元素
private void removeElement() {
HitRate hr = Collections.min(count.values());
cache.remove(hr.key);
count.remove(hr.key);
}
//更新訪問元素狀態
private void addHitCount(k key) {
HitRate hitRate = count.get(key);
hitRate.hitCount = hitRate.hitCount + 1;
hitRate.lastTime = System.nanoTime();
}
//內部類
class HitRate implements Comparable<HitRate> {
private k key;
private int hitCount;
private long lastTime;
private HitRate(k key, int hitCount, long lastTime) {
this.key = key;
this.hitCount = hitCount;
this.lastTime = lastTime;
}
@Override
public int compareTo(HitRate o) {
int compare = Integer.compare(this.hitCount, o.hitCount);
return compare == 0 ? Long.compare(this.lastTime, o.lastTime) : compare;
}
}
public static void main(String[] args) {
LFU<Integer, Integer> cache = new LFU<>(3);
cache.put(2, 2);
cache.put(1, 1);
System.out.println(cache.get(2));
System.out.println(cache.get(1));
System.out.println(cache.get(2));
cache.put(3, 3);
cache.put(4, 4);
//1、2元素都有訪問次數,放入3後快取滿,加入4時淘汰3
System.out.println(cache.get(3));
System.out.println(cache.get(2));
//System.out.println(cache.get(1));
System.out.println(cache.get(4));
cache.put(5, 5);
//目前2訪問2次,1訪問一次,4訪問一次,由於4的時間比較新,放入5的時候移除1元素。
System.out.println("-=-=-=-");
cache.cache.entrySet().forEach(entry -> {
System.out.println(entry.getValue());
});
}
}
這裡實現略微複雜,首先要維持一個快取的map還要維持一個訪問次數以及時間的map。
1、首先內部類有3個屬性,有快取的key,快取的訪問次數,最近一次的訪問時間。內部實現比較器,按照先訪問次數後時間的比較順序。整個類作為物件放在一個hashmap的value裡。
2、put方法:當一個元素要放入快取的時候,先去快取檢查是否有相同的key,也就是要填加的(key,value)在cache的map裡get(key)是否為空。這裡不用檢查value是否有相同的,標識一個快取的唯一性是key,key不同就是一個不同的快取元素。
當get(key)為空時,要放入快取新元素了,首先檢查快取的容量是否達到限制值,達到了,執行移除一個元素的方法,然後在count的map里加入相應的訪問資訊,初始值為1。如果沒達到限制值,直接在count的map里加入相應的訪問資訊,初始值為1。
當get(key)不為空直接執行addHitCount方法。將訪問次數加1,更新最新訪問時間。
最後不管什麼情況,執行map.put方法。
執行結果:
2
1
2
null
2
4
-=-=-=-
2
4
5
開始加入1,2,訪問1,一次2兩次。加入3,在加入4.發現快取元素為124.原因是1,2都有訪問次數,3和4在時間上,比較古老,會被淘汰。
兩種實現方法是在各大部落格上學習後,理解後,自己小小的改造了一下所總結,謝謝各位大神。