QQ羣:372135639
LRU是Least Recently Used 近期最少使用算法。
內存管理的一種頁面置換算法,對於在內存中但又不用的數據塊(內存塊)叫做LRU,操作系統會根據哪些數據屬於LRU而將其移出內存而騰出空間來加載另外的數據。
什麼是LRU算法? LRU是Least Recently Used的縮寫,即最少使用頁面置換算法,是爲虛擬頁式存儲管理服務的。
關於操作系統的內存管理,如何節省利用容量不大的內存爲最多的進程提供資源,一直是研究的重要方向。而內存的虛擬存儲管理,是現在最通用,最成功的方式—— 在內存有限的情況下,擴展一部分外存作爲虛擬內存,真正的內存只存儲當前運行時所用得到信息。這無疑極大地擴充了內存的功能,極大地提高了計算機的併發度。虛擬頁式存儲管理,則是將進程所需空間劃分爲多個頁面,內存中只存放當前所需頁面,其餘頁面放入外存的管理方式。
說明:
對於虛擬頁式存儲,內外存信息的替換是以頁面爲單位進行的——當需要一個放在外存的頁面時,把它調入內存,同時爲了保持原有空間的大小,還要把一個內種調動越少,進程執行的效率也就越高。那麼,把哪個頁面調出去可以達到調動儘量少的目的?我們需要一個算法。
其實,達到這樣一種情形的算法是最理想的了——每次調換出的頁面是所有內存頁面中最遲將被使用的——這可以最大限度的推遲頁面調換,這種算法,被稱爲理想頁面置換算法。可惜的是,這種算法是無法實現的。
爲了儘量減少與理想算法的差距,產生了各種精妙的算法,最少使用頁面置換算法便是其中一個。LRU算法的提出,是基於這樣一個事實:在前面幾條指令中使用頻繁的頁面很可能在後面的幾條指令中頻繁使用。反過來說,已經很久沒有使用的頁面很可能在未來較長的一段時間內不會被用到。這個,就是著名的局部性原理——比內存速度還要快的cache,也是基於同樣的原理運行的。因此,我們只需要在每次調換時,找到最少使用的那個頁面調出內存。這就是LRU算法的全部內容。
FIFO 、LRU、LFU三種算法
提到緩存,有兩點是必須要考慮的:
(1)緩存數據和目標數據的一致性問題。
(2)緩存的過期策略(機制)。
其中,緩存的過期策略涉及淘汰算法。常用的淘汰算法有下面幾種:
(1)FIFO:First In First Out,先進先出
(2)LRU:Least Recently Used,最近最少使用
(3)LFU:Least Frequently Used,最不經常使用
注意LRU和LFU的區別。LFU算法是根據在一段時間裏數據項被使用的次數選擇出最少使用的數據項,即根據使用次數的差異來決定。而LRU是根據使用時間的差異來決定的。
一個優秀的緩存框架必須實現以上的所有緩存機制。例如:Ehcache就實現了上面的所有策略。
雙鏈表+hashtable(有些面試要考)
package com.example.test;
import java.util.Hashtable;
public class LRUCache{
private int cacheSize;
private Hashtable<Object,Entry> nodes;
private int currentSize;
private Entry first; //鏈表頭
private Entry last; //鏈表尾
public LRUCache(int i){
currentSize = 0;
cacheSize = i;
nodes = new Hashtable<Object,Entry>(i);//緩存容器
}
/**
*獲取緩存中的對象,並把它放在最前面
*/
public Entry get(Object key){
Entry node = nodes.get(key);
if(node != null){
moveToHead(node);
return node;
}else{
return null;
}
}
/**
* 添加
*/
public void put(Object key,Object value){
//先看看hashtable是否存在該entry,如果存在,則只更新其
Entry node = nodes.get(key);
if(node == null){
if(currentSize >= cacheSize){
nodes.remove(last.key);
removeLast();
}else{
currentSize++;
}
node = new Entry();
}
node.value = value;
//將最新使用的節點放在鏈表頭,表示最新使用
moveToHead(node);
nodes.put(key, node);
}
public void remove(Object key){
Entry node = nodes.get(key);
//在鏈表中刪除
if(node != null){
if(node.prev != null){
node.prev.next = node.next;
}
if(node.next != null){
node.next.prev = node.prev;
}
if(last == node)
last = node.prev;
if(first == node)
first = node.next;
}
//在hashtable中刪除
nodes.remove(key);
}
/**
* 刪除鏈表尾部節點,即使用最後 使用的entry
*/
private void removeLast(){
//鏈表尾不爲空,則將鏈表尾指向null,刪除連表尾(刪除最少引用)
if(last != null){
if(last.prev != null)
last.prev.next = null;
else
first = null;
last = last.prev;
}
}
/**
*移動到鏈表頭,表示最新使用過
*/
private void moveToHead(Entry node){
if(node == first)
return;
if(node.prev != null)
node.prev.next = node.next;
if(node.next != null)
node.next.prev = node.prev;
if(last == node)
last =node.prev;
if(first != null){
node.next = first;
first.prev = node;
}
first = node;
node.prev = null;
if(last == null){
last = first;
}
}
/*
* 清空緩存
*/
public void clear(){
first = null;
last = null;
currentSize = 0;
}
}
class Entry{
Entry prev;
Entry next;
Object value;
Object key;
}