Java〖 LRU緩存機制〗力扣146
運用你所掌握的數據結構,設計和實現一個 LRU (最近最少使用) 緩存機制。它應該支持以下操作: 獲取數據 get 和 寫入數據 put 。
- 獲取數據 get(key) - 如果關鍵字 (key) 存在於緩存中,則獲取關鍵字的值(總是正數),否則返回 -1。
- 寫入數據 put(key, value) - 如果關鍵字已經存在,則變更其數據值;如果關鍵字不存在,則插入該組「關鍵字/值」。當緩存容量達到上限時,它應該在寫入新數據之前刪除最久未使用的數據值,從而爲新的數據值留出空間。
進階:
你是否可以在 O(1) 時間複雜度內完成這兩種操作?
示例:
LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 該操作會使得關鍵字 2 作廢
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 該操作會使得關鍵字 1 作廢
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
一. 分析 LinkedHashMap
LRU是Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換算法,要如何實現呢?
1.1 LinkedHashMap實現
首先看到了LinkedHashMap 底層是一個Entery結點,爲了實現插入快有序和快速訪問兩個優點,結合了HashMap與雙向鏈表
這邊不深入去講,主要來說說LinkedHashMap存儲數據是有序的,而且分爲兩種:插入順序和訪問順序。下圖主要爲訪問順序時結點變更情況
- 插入順序 表示LinkedHashMap中存儲的順序是按照調用put方法插入的順序進行排序的(默認設置爲: false)
HashMap中有Entry1、Entry2、Entry3,並設置LinkedHashMap爲訪問順序,則更新Entry1時,會先把Entry1從雙向鏈表中刪除,然後再把Entry1加入到雙向鏈表的表尾,而Entry1在HashMap結構中的存儲位置沒有變化,對比圖如下所示:
這時候,有些小夥伴就會驚訝的發現,這不就是LRU本U嗎? 但其實LinkedHashMap是會不斷擴容的,要想真正實現LRU,你得去重寫removeEldestEntry()方法
重寫removeEldestEntry方法,當達到條件,就返回ture ,返回true就刪除最近最少使用的Entery結點
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest){ //重寫removeEldestEntry方法,當達到條件,就返回ture ,返回true就刪除最近最少使用的Entery結點
return super.size()>capacity;
}
1.2 上代碼(LinkedHashMap實現)
/**
* 繼承LinkedHashMap實現,將構造方法實現成按訪問順序(true),(false)爲按插入順序即爲FIFO
*/
class LRUCache extends LinkedHashMap<Integer,Integer> {
int capacity;
public LRUCache(int capacity) {
super(capacity,075f,true);
this.capacity=capacity;
}
public int get(int key) {
return super.getOrDefault(key,-1); //這裏找不到就返回-1
}
public void put(int key, int value) {
super.put(key,value);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest){ //重寫removeEldestEntry方法,當達到條件,就返回ture ,返回true就刪除最近最少使用的Entery結點
return super.size()>capacity;
}
}
繼承LinkedHashMap實現,將構造方法實現成按訪問順序(true),(false)爲按插入順序即爲FIFO
二. 手寫LRU
既然原理都懂了,不如我們自己手寫一個LRU加深印象,不就是一個HashMap跟雙向鏈表嗎,幹他就完了
2.1 分析
雙向鏈表與hashmap結合
-
對於get操作,判斷是否插已插入
- 已插入的話,我們通過hash查到當前結點,更新value,並放到鏈表最頭
- 對於沒插入的結點,那我們通過頭插法插入鏈表最前,然後再判斷當前容量是否大於設置容量,大於的話刪除最尾結點
-
對於put操作,判斷key是否存在
- 已存在的話,我們利用hash找到,並放到表頭,返回該結點的值
- 不存在的話返回-1即可
2.2 雙向鏈表
class DoubleList{ //雙向鏈表
int key;
int value;
DoubleList pre;
DoubleList next;
public DoubleList(int key, int value) {
this.key = key;
this.value = value;
}
public DoubleList() {
}
}
2.3 LRU實現類
註釋基本都有,哪裏出錯了評論區見
class LRUCache1 {
int size; //當前容量
int capacity; //最大容量
DoubleList head; //頭指針
DoubleList tail; //尾指針
Map<Integer,DoubleList> map=new HashMap<>(); //hash快速查找
public LRUCache1(int capacity) {
size=0;
this.capacity=capacity;
head=new DoubleList();
tail=new DoubleList();
head.next=tail;
tail.pre=head;
}
/**
* 查找該結點
* @param key
* @return
*/
public int get(int key) {
DoubleList findVal = map.get(key);
if(findVal!=null){
moveToHead(findVal);
return findVal.value;
}else return -1;
}
/**
* 新增結點
* @param key
* @param value
*/
public void put(int key, int value) {
DoubleList findVal = map.get(key); //查詢是否存在該key
if(findVal==null){ //不存在
DoubleList doubleList = new DoubleList(key, value); //新建這個結點
map.put(key,doubleList); //扔到map中去
addToHead(doubleList); //放到鏈表頭部
size++; //自增容量
if(size>capacity) { //若當前容量大於最大容量
DoubleList removeTail = removeTail(); //找到最後一個結點,刪除
map.remove(removeTail.key); //在map中刪除該key
size--;
}
}else { //若該結點存在
findVal.value=value; //更新這個結點的值
moveToHead(findVal); //刪除並移動到鏈表頭部
}
}
/**
* 包含兩個操作
* 1.在鏈表中刪除該結點
* 2.將該結點加到鏈表頭部
* @param doubleList
*/
private void moveToHead(DoubleList doubleList){
removeNode(doubleList);
addToHead(doubleList);
}
/**
* 在鏈表中刪除該結點
* @param doubleList
*/
private void removeNode(DoubleList doubleList){
doubleList.pre.next=doubleList.next;
doubleList.next.pre=doubleList.pre;
}
/**
* 將該結點頭插到鏈表中
* @param node
*/
private void addToHead(DoubleList node){
// node.pre = head;
// node.next = head.next;
// head.next.pre = node;
// head.next = node;
node.next=head.next;
node.pre=head;
head.next.pre=node;
head.next=node;
}
/**
* 移除隊尾元素
* @return 返回被刪除的結點(爲了刪除map中的值,所以此處返回結點)
*/
private DoubleList removeTail() {
DoubleList temp=tail.pre;
removeNode(temp);
return temp;
}
}