LRU 快取淘汰演算法的兩種實現

弒曉風發表於2019-03-02
It's always the attention to detail and the little grace notes that really make something sing. 專注細節愈出彩,別緻心思定成敗。

本文主要分享了LRU 快取淘汰演算法兩種實現。重要的不是實現,而且思想!

所有原始碼均已上傳至github:連結

定義

LRU(Least Recently Used)最近最少使用策略就像它的名字一樣,是根據資料的歷史訪問記錄來進行淘汰資料的,其思想是“如果資料最近被訪問過,那麼將來被訪問的機率也更高;長期不被使用的資料在將來用到的機率也不大;當資料所佔記憶體達到一定的閾值時,將移除最近最少被使用的資料”。

舉例

比如一個書櫃(容量為10),我會把我自己的書籍放進去,其中有兩本書籍是我最喜歡的,經常翻閱,但是隨著我的購買,書櫃會逐漸放滿(記憶體溢位),這時候就需要用到LRU的思想了,把我不經常看的,從書櫃裡拿出,放進箱子裡,然後再把新買的書籍放進書櫃。

再 比如Redis,它是基於記憶體的,但是記憶體也不是無窮大的,當記憶體佔用達到一個閾值的時候,它就可以使用LRU等一系列快取演算法,或者是將資料存到硬碟裡。

具體的實現程式碼如下:

基於連結串列

連結串列初始化,申請capacity大小的記憶體空間

    private LRUByLinkedList(int capacity) {
        head = null;
        size = capacity;
        count = 0;
    }複製程式碼

模擬LRU的訪問(如果沒有該資料,則插入頭部)

注意:該if-else語句不能交換順序,否則會出現連結串列已滿,並且該資料已存在的情況無法處理。

    private void insert(int data) {
        Node preNode = findNode(data);
        if (null != preNode) {
            delete(preNode);
        } else {
            if (count >= size) {//連結串列滿
                deleteToTail();
            }
        }
        insertToHead(data);
    }複製程式碼

刪除指定資料方法,常規刪除

    private void delete(Node preNode) {
        System.out.println("刪除指定元素:" + preNode.next.data);
        preNode.next = preNode.next.next;
        --count;
    }複製程式碼

刪除連結串列尾部資料

    private void deleteToTail() {
        Node node = head;
        Node curNode = null;
        while (null != node.next) {
            curNode = node;
            node = node.next;
        }
        if (null != curNode) {
            System.out.println("連結串列已滿,刪除尾部元素:" + curNode.next.data);
            curNode.next = null;
        }
        --count;
    }複製程式碼

在頭部插入資料

    private void insertToHead(int data) {
        Node node = new Node(data, null);
        if (null == head) {
            head = node;
        } else {
            node.next = head;
        }
        head = node;
        ++count;
    }
複製程式碼

測試結果如下

  1. 首先將capacity大小的連結串列插滿
  2. 當插入capacity+1個資料時,需要刪除尾部資料
  3. 當插入的資料存在的時候,將它從其位置刪除,並且插入頭部

LRU 快取淘汰演算法的兩種實現

基於陣列

資料比較連結串列實現起來更為簡單,在此不做闡述,直接上程式碼。

    private LRUByArray(int capacity) {
        arrays = new int[capacity];
        size = capacity;
        count = 0;
    }複製程式碼

    private void insert(int data) {
        int index = findValue(data);
        if (-1 != index) {
            delete(index);
        } else {
            if (count >= size) {//陣列滿
                deleteToTail();
            }
        }
        insertToHead(data);
    }複製程式碼

    private void delete(int index) {
        //通過資料遷移的方式將該值刪除
        for (int i = index + 1; i < count; i++) {
            arrays[i - 1] = arrays[i];
        }
        System.out.println("刪除元素...");
        --count;
    }複製程式碼

    private void deleteToTail() {
        --count;//標記刪除法,實際上陣列還存有該元素
        System.out.println("刪除尾部元素...");
    }複製程式碼

    private void insertToHead(int data) {
        if (count > 1) {
            for (int i = count - 1; i > -1; --i) {
                arrays[i + 1] = arrays[i];
            }
        }
        arrays[0] = data;
        ++count;
    }複製程式碼

測試結果如下

LRU 快取淘汰演算法的兩種實現

注意:因為有大量頻繁的訪問導致資料遷移頻繁,因此陣列並不適合做這種事情,可以考慮加一個HaspMap做快取,避免資料的頻繁遷移。

end

LRU 快取淘汰演算法的兩種實現

您的點贊和關注是對我最大的支援,謝謝!


相關文章