一,LRU算法介紹
LRU是內存分配中“離散分配方式”之分頁存儲管理方式中用到的一個算法。每個進程都有自己的頁表,進程只將自己的一部分頁面加載到內存的物理塊中,當進程在運行過程中,發現某頁面不在物理內存塊中(發生缺頁異常)就需要從磁盤把相應的頁面調入內存。而若內存已經滿了的情況下,需要將內存中暫時不用的物理塊頁面 換出到磁盤(交換空間)中,那到底換出哪一頁呢?LRU算法就是用來解決到底換出哪一頁 的這個問題。
LRU算法是最近最少未使用算法。當內存缺頁時,總是優先選出距離當前最久未使用的頁面換出,並把當前的缺頁換入。該算法可用棧模擬實現。
棧頂總是保存當前最近訪問的頁面號,棧底則總是保存最久未訪問的頁面號。對於下一個頁面,有兩種情況:
①命中,則需要:更新棧頂元素。即將當前命中的頁面號放到棧頂。
②未命中,這里還需要考慮棧是否滿了。1)若棧未滿,直接將未命中的頁面號放到棧頂即可。為什么要放到棧頂(LinkedList表頭)呢?
因為,LRU每次總是選取最近最久未被訪問的頁面淘汰。某頁面剛剛被訪問,需要放到棧頂,以表示它不是“最近最久 未訪問的頁面”
2)棧已經滿了,則需要選中一頁換出(棧底元素是最久未訪問的頁面),然后再將新頁面放入棧頂。
二,代碼實現
import java.util.LinkedList; public class LRU { private LinkedList<Integer> stack;//模擬頁面'寄存器' private int size;//寄存器大小,表示一共可裝入多少頁面 public LRU(int size) { stack = new LinkedList<>(); this.size = size; } //LRU算法簡單實現,返回一共未命中的次數 public int lru(int[] pageNumbers) { if(size <= 0 || pageNumbers == null) throw new IllegalArgumentException("illegal arugments"); if(pageNumbers.length <= size) return pageNumbers.length; int unhit = 0; for(int i = 0; i < pageNumbers.length; i++) { int index = isHit(pageNumbers[i]); if(index == -1) unhit = processUnHit(pageNumbers[i], unhit); else { processHit(pageNumbers[i], index); } } return unhit; } /** * * @param pageNumber 判斷 pageNumber是否hit * @return -1 表示 unhit, 其他表示hit */ private int isHit(int pageNumber){ return stack.indexOf(pageNumber); } /** * 當棧未滿時,未命中的頁面號直接入棧;棧滿時,需要替換頁面,先選中一個頁面(棧底)刪除,然后Push新頁面 * @param pageNumber 未命中的頁面號 * @param count 當前未命中次數 * @return 更新后的未命中的次數 */ private int processUnHit(int pageNumber, int count){ if(isFull()) stack.removeLast();//刪除最久未訪問的頁面 stack.push(pageNumber);//放入最近訪問的頁面 count++;//未命中的次數加1 return count; } //處理命中的情況 private void processHit(int pageNumber, int index){ stack.push(stack.remove(index)); } //判斷'寄存器'棧是否已經滿了 private boolean isFull() { if(stack.size() < size) return false; else return true; } //test public static void main(String[] args) { int[] pageNumbers = {4,7,1,1,7,2,1}; int size = 2; LRU lru = new LRU(size); System.out.println(lru.lru(pageNumbers)); } }
三,復雜度分析
由於java.util.LinkedList 實現了棧的功能。push()方法總是將元素放到表頭,pop()方法總是從鏈表的表頭刪除元素。在這里,鏈表的表頭代表棧頂。
因此,當某頁面號命中時,需要從鏈表中找到該頁面的位置(index),然后刪除該頁面,並將它push到鏈表的表頭。---processHit()方法
由於是鏈表,故尋找某頁面的時間復雜度為O(N),最壞情況下掃描整個鏈表。
當頁面未命中的,需要將頁面號push到鏈表表頭。push之前,先檢查棧是否已經滿了。若未滿,直接push入棧,時間復雜度為O(1);如果棧已經滿了,需要刪除鏈表的表尾元素(相當於棧底元素--removeLast()時間復雜度也為O(1),因為LinkedList本質上是一個雙向鏈表。)然后,再將該刪除的元素push到棧頂--時間復雜度為O(1)
故未命中時,處理的總的時間復雜度還是O(1)