基于單鏈表的 LRU 算法實(shí)現(xiàn)

本文首發(fā)于 LOGI'S BLOG,由作者轉(zhuǎn)載。

在使用頁(yè)進(jìn)行內(nèi)存管理的操作系統(tǒng)中,當(dāng)新頁(yè)進(jìn)入內(nèi)存且內(nèi)存已滿時(shí),需要 頁(yè)面置換算法 決定哪個(gè)頁(yè)應(yīng)該被替換。

缺頁(yè)中斷

當(dāng)正在運(yùn)行的程序訪問一個(gè)被映射于虛擬內(nèi)存卻未被加載到物理內(nèi)存中的內(nèi)存頁(yè)時(shí),缺頁(yè)中斷便發(fā)生了。由于真實(shí)物理內(nèi)存遠(yuǎn)小于虛擬內(nèi)存,缺頁(yè)中斷無(wú)法避免。缺頁(yè)中斷發(fā)生時(shí),操作系統(tǒng)不得不將某個(gè)已加載的內(nèi)存頁(yè)替換為新的正被需要的頁(yè)面。不同頁(yè)面置換算法決定被替換頁(yè)的方式各不相同,但所有算法都致力于減少缺頁(yè)中斷次數(shù)。

常見頁(yè)面置換算法

最佳置換 Optimal Page Replacement(OPT)

在該算法中,那些將來(lái)最長(zhǎng)時(shí)間不會(huì)被使用的頁(yè)面會(huì)被替換。最佳置換算法最大程度地減少了缺頁(yè)中斷,但它不可實(shí)現(xiàn),因?yàn)椴僮飨到y(tǒng)無(wú)法得知將來(lái)的請(qǐng)求,它的最大意義在于為評(píng)估其他頁(yè)面置換算法的性能提供指標(biāo)。

先進(jìn)先出 First In First Out(FIFO)

這是最簡(jiǎn)單的置換算法。在該算法中,操作系統(tǒng)以隊(duì)列形式持續(xù)跟蹤位于內(nèi)存中的頁(yè)面,年齡最長(zhǎng)的頁(yè)面位于隊(duì)列最前面。發(fā)生缺頁(yè)中斷時(shí),隊(duì)列最前面的頁(yè)面將被替換。

Belady’s Anomaly 畢雷迪異常:采用 FIFO 算法時(shí),如果對(duì)一個(gè)進(jìn)程未分配它所要求的全部頁(yè)面,有時(shí)就會(huì)出現(xiàn)分配的頁(yè)面數(shù)增多,缺頁(yè)率反而提高(時(shí)高時(shí)低)的異常現(xiàn)象。

原因:FIFO 算法的置換特征與進(jìn)程訪問內(nèi)存的動(dòng)態(tài)特征是矛盾的,即被置換的頁(yè)面并不是進(jìn)程不會(huì)訪問的。

最少使用 Least Frequently Used(LFU)

該算法為每個(gè)頁(yè)面設(shè)置一個(gè)訪問計(jì)數(shù)器,頁(yè)面被訪問時(shí),計(jì)數(shù)加 1,缺頁(yè)時(shí),置換計(jì)數(shù)最小的頁(yè)面。其缺陷是開始時(shí)頻繁使用,但以后不再使用的頁(yè)面很難被置換。此外新加入的頁(yè)面因訪問次數(shù)較少,可能很快又被置換出去。

最近最少使用 Least Recently Used (LRU)

該算法選擇最長(zhǎng)時(shí)間沒有被引用的頁(yè)面進(jìn)行置換。具體做法是,記錄每個(gè)邏輯頁(yè)面的上一次訪問時(shí)間,發(fā)生缺頁(yè)中斷時(shí),淘汰從上一次使用到此刻間隔最長(zhǎng)的頁(yè)面。根據(jù) 程序執(zhí)行與數(shù)據(jù)訪問的局部性原理,如果某些頁(yè)面長(zhǎng)時(shí)間未被訪問,則它們?cè)趯?lái)有很大可能仍然長(zhǎng)時(shí)間不被訪問,所以 LRU 的性能最接近于 OPT。

基于單鏈表實(shí)現(xiàn) LRU

問題:運(yùn)用你所掌握的數(shù)據(jù)結(jié)構(gòu),設(shè)計(jì)和實(shí)現(xiàn)一個(gè) LRU 緩存機(jī)制。它應(yīng)該支持以下操作:獲取數(shù)據(jù) get 和 寫入數(shù)據(jù) put。

獲取數(shù)據(jù) get(key):如果密鑰 (key) 存在于緩存中,則獲取密鑰的值(總是正數(shù)),否則返回 -1。

寫入數(shù)據(jù) put(key, value):如果密鑰不存在,則寫入其數(shù)據(jù)值。當(dāng)緩存容量達(dá)到上限時(shí),它應(yīng)該在寫入新數(shù)據(jù)之前刪除最近最少使用的數(shù)據(jù)值,從而為新的數(shù)據(jù)值留出空間。

// 示例:
LRUCache cache = new LRUCache( 2 /* 緩存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 該操作會(huì)使得密鑰 2 作廢
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 該操作會(huì)使得密鑰 1 作廢
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4

思路:維護(hù)一個(gè)有序單鏈表,規(guī)定越靠近表尾的結(jié)點(diǎn)是越早訪問的。當(dāng)訪問新頁(yè)面 get(key) 時(shí),從表頭遍歷鏈表,如果該頁(yè)面已存在,則將其從原來(lái)位置刪除,然后插入到表頭。加載頁(yè)面 put(key,value) 時(shí),若該頁(yè)面不存在且鏈表未滿,則將頁(yè)面插入表頭。否則,刪除表尾結(jié)點(diǎn),再將新頁(yè)面插入表頭。

存儲(chǔ)密度:2/3

空間復(fù)雜度:O(1)

時(shí)間復(fù)雜度:遍歷鏈表 O(n),插入刪除 O(1),因此總的時(shí)間復(fù)雜度 O(n)

package com.logi.algorithm;

/**
 * @author LOGI
 * @version 1.0
 * @date 2019/7/9 18:02
 */
public class LRUWithSinglyLinkedList {
    LRUNode head;
    int capacity;
    int size;

    public LRUWithSinglyLinkedList(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        this.head = new LRUNode();
    }

    public static void main(String[] args) {
        LRUWithSinglyLinkedList lru = new LRUWithSinglyLinkedList(2);
        lru.put(1, 1);
        System.out.println(lru + ", after put(1,1)");
        lru.put(2, 2);
        System.out.println(lru + ", after put(2,2)");
        lru.get(1);
        System.out.println(lru + ", after get(1)");
        lru.put(3, 3);
        System.out.println(lru + ", after put(3,3)");
        lru.get(2);
        System.out.println(lru + ", after get(2)");
        lru.put(4, 4);
        System.out.println(lru + ", after put(4,4)");
        lru.get(1);
        System.out.println(lru + ", after get(1)");
        lru.get(3);
        System.out.println(lru + ", after get(3)");
        lru.get(4);
        System.out.println(lru + ", after get(4)");
    }

    @Override
    public String toString() {
        LRUNode current = this.head.next;
        StringBuilder list = new StringBuilder();
        while (current != null) {
            list.append(current.value);
            if (current.next != null) {
                list.append("->");
            }
            current = current.next;
        }
        return list.toString();
    }

    /**
     * 根據(jù) key 查找 value,如果已存在將其移至表頭并返回,否則返回 -1
     *
     * @param key
     * @return
     */
    public int get(int key) {
        LRUNode prev = this.getPrev(key);
        if (prev != null) {
            LRUNode current = prev.next;
            this.delete(prev);
            this.insert(current);
            return current.value;
        } else {
            return -1;
        }
    }

    /**
     * 加載頁(yè)面,如果緩存已滿,刪掉表尾
     *
     * @param key
     * @param value
     */
    public void put(int key, int value) {
        LRUNode current = new LRUNode(key, value);
        LRUNode prev = this.getPrev(key);
        if (prev == null) {
            if (this.size == this.capacity) {
                this.delete(this.getTailPrev());
            }
            this.insert(current);
        }
    }

    /**
     * 獲取 tail 前驅(qū)
     *
     * @return
     */
    public LRUNode getTailPrev() {
        LRUNode current = this.head;
        if (current.next == null) {
            return null;
        }
        while (current.next.next != null) {
            current = current.next;
        }
        return current;
    }

    /**
     * 根據(jù) key 獲得前驅(qū)
     *
     * @param key
     * @return
     */
    public LRUNode getPrev(int key) {
        LRUNode prev = this.head;
        while (prev != null) {
            if (prev.next != null && prev.next.key == key) {
                break;
            }
            prev = prev.next;
        }
        return prev;
    }

    /**
     * 根據(jù)前驅(qū)刪除
     *
     * @param prev
     */
    public void delete(LRUNode prev) {
        prev.next = prev.next.next;
        this.size--;
    }

    /**
     * 插入到表頭
     *
     * @param current
     */
    public void insert(LRUNode current) {
        current.next = this.head.next;
        this.head.next = current;
        this.size++;
    }
}

class LRUNode {
    LRUNode next;
    int key;
    int value;

    public LRUNode() {
        this.key = this.value = 0;
        this.next = null;
    }

    public LRUNode(int key, int value) {
        this.key = key;
        this.value = value;
        this.next = null;
    }
}

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 8.1虛擬存儲(chǔ)的需求背景 虛擬內(nèi)存是非連續(xù)內(nèi)存分配的一個(gè)延續(xù),非連續(xù)內(nèi)存分配在存儲(chǔ)空間內(nèi)可以連續(xù)也可以不連續(xù)。虛擬...
    龜龜51閱讀 6,254評(píng)論 2 6
  • 一、實(shí)驗(yàn)?zāi)康募盎疽?設(shè)計(jì)和實(shí)現(xiàn)最佳置換算法、先進(jìn)先出置換算法、最近最久未使用置換算法、頁(yè)面緩沖置換算法,改進(jìn)C...
    二磊Jerly閱讀 1,240評(píng)論 0 0
  • 1. 虛擬存儲(chǔ)器的基本概念 分析常規(guī)存儲(chǔ)器管理不足的原因: 1)常規(guī)存儲(chǔ)器管理方式的特征 一次性:作業(yè)在運(yùn)行前一...
    Whocare_2f87閱讀 1,182評(píng)論 0 0
  • 1. 虛擬存儲(chǔ)器的基本概念 分析常規(guī)存儲(chǔ)器管理不足的原因: 1)常規(guī)存儲(chǔ)器管理方式的特征 一次性:作業(yè)在運(yùn)行前一...
    盆栽木只閱讀 1,430評(píng)論 0 0
  • 一、虛擬存儲(chǔ)技術(shù) 所謂虛擬存儲(chǔ)技術(shù)是指:當(dāng)進(jìn)程運(yùn)行時(shí),先將其一部分裝入內(nèi)存,另一部分暫留在磁盤,當(dāng)要執(zhí)行的指令或訪...
    yjaal閱讀 3,731評(píng)論 0 6

友情鏈接更多精彩內(nèi)容