1. 程式人生 > >LRU快取淘汰演算法分析與實現

LRU快取淘汰演算法分析與實現

概述

記錄一下LRU快取淘汰演算法的實現。

原理

LRU(Least recently used,最近最少使用)快取演算法根據資料最近被訪問的情況來進行淘汰資料,其核心思想是“如果資料最近被訪問過,那麼將來被訪問的機率也更高”。

介紹

下圖中,介紹了一個快取空間為5的快取佇列,當訪問資料的順序是:1,2,3,4,5,6,7,6,4,0時空間中資料的變化過程。
這裡寫圖片描述
可以發現:
1. 當快取空間未滿時,資料一直往新的空間寫;
2. 當快取滿,並且快取中沒有需要訪問的資料時,最先進入快取的資料被淘汰掉;
3. 當快取滿,並且快取中有需要訪問的資料時,做了一個數據交換,把訪問的資料拿出來,其餘資料往下壓,最後把訪問的資料放到頂部
在這裡,可能有疑問,就是把“資料交換”於“資料完全新增和刪除”有什麼區別呢?答案是效能,前者是移動指標,後者是更新整個記憶體空間,後者所花費的系統開銷遠比前者大得多。

實現

看了演算法的介紹,我們想到的資料結構就是連結串列了。

  • 雙向連結串列的資料結構
    /**
     * 雙向連結串列資料結構
     */
    private class NodePair{
        NodePair frontNode;
        NodePair postNode;
        int data;
    }
  • 逆序查詢連結串列
    /**
     * 根據資料逆序查詢連結串列中是否有此節點,有,則把該點提出來,放到current的位置
     * 當匹配到的時候,返回true
     * @param data
     */
public boolean searchNode(int data){ boolean flag = false; NodePair tempNode = current; //不匹配,即沒找到,則繼續查詢 while(tempNode.frontNode != null || tempNode.data != data){ tempNode = tempNode.frontNode; } //這個判讀表示匹配到了 if(tempNode.data == data){ tempNode.frontNode.postNode = tempNode.postNode; tempNode.postNode.frontNode = tempNode.frontNode; current = tempNode; flag = true
; } return flag; }
  • 空間滿了,並且快取中沒有待訪問的資料,刪除最下面的節點,再新增一個節點,相當於重新賦值最下面的節點,如圖
    這裡寫圖片描述
    紅線表示,head將要指向倒數第二個點了,即,倒數第二個點要變成現在最底下的點了。
    /**
     * 給head節點重新賦值操作
     * 實現細節是:
     * 0.倒數第二個點(head的下一個點)的frontNode引用指向null
     * 1.給head所指節點重新賦值
     * 2.current節點的frontNode引用指向head
     * 3.把current節點指向head
     * 4.把head指向head的下一個節點(即,倒數第二個點)
     */
    public void resetHeadNode(int data){
        NodePair secondNode = head.postNode;

        head.postNode.frontNode = null;

        head.data = data;
        head.frontNode = current;
        head.postNode = null;

        current.postNode = head;

        current = head;

        head = secondNode;
    }
  • 快取滿了,查詢快取中是否有待訪問資料,有的話,同時把有的資料放到current指標所指位置。
    /**
     * 根據資料逆序查詢連結串列中是否有此節點,有,則把該點提出來,放到current的位置
     * 當匹配到的時候,返回true
     * @param data
     */
    public boolean searchNode(int data){
        boolean flag = false;
        NodePair tempNode = current;
        //不匹配,即沒找到,則繼續查詢
        while(tempNode.frontNode != null || tempNode.data != data){
            tempNode = tempNode.frontNode;
        }
        //這個判讀表示匹配到了
        if(tempNode.data == data){
            tempNode.frontNode.postNode = tempNode.postNode;
            tempNode.postNode.frontNode = tempNode.frontNode;
            current = tempNode;
            flag = true;
        }

        return flag;
    }
  • 新增節點
    /**
     * 往LRU快取中插入資料
     * @param data
     */
    public void addNode(int data){
        //快取未滿,不需要刪除,直接插入
        if(length <= size){
            NodePair tempNode = new NodePair();
            tempNode.frontNode = current;
            tempNode.postNode = null;
            tempNode.data = data;
            current = tempNode;
            length++;
        }
        //快取滿了,查詢快取中有沒有資料
        else{
            if(!searchNode(data)){
                //快取中沒有,需要給head節點重新賦值
                resetHeadNode(data);
            }
        }
    }

LRU演算法的缺點

如果有幾個不符合“如果資料最近被訪問過,那麼將來被訪問的機率也更高”的規律時,會破壞快取,導致效能下降。

總結

寫演算法時,通過畫圖,寫步驟,先產生一個清晰的思路,然後一步步去做實現剛才思考的步驟。