算法 | 鏈表的應用,緩存失效算法

我們知道,緩存是一種提高數據讀取性能的技術,在硬件涉及,軟件開發中有着非常廣泛的應用。在使用緩存時我們最大的問題就是緩存的數據是不能太多的,當緩存被佔滿時,我們如何決定哪些數據被清除,哪些數據被保留呢?這個時候,我們就會採用一些緩存失效算法來處理。今天這篇文章我們就一起學習下常見的緩存失效算法通過鏈表如何實現。

先進先出策略(FIFO,First In First Out)

這種策略的實現是最簡單的了,我們利用鏈表的特性,只要將需要緩存的數據依次添加到鏈表中,當空間不夠時,將鏈表末尾的數據刪除即可(如果我們對隊列比較熟悉的話,可以發現這種策略用隊列進行實現是最方便的)

實現代碼如下:

package com.study.spring.transaction.cache;

import lombok.AllArgsConstructor;

import java.security.InvalidParameterException;

/**
 * @author dmz
 * @date Create in 20:57 2019/8/10
 */
public class Main {
    public static void main(String[] args) {
        link link = new link(4);
        link.add(1);
        link.add(2);
        link.add(3);
        link.add(4);
        link.add(5);
        link.add(6);
        link.add(7);
        link.sout();
    }

    static class link {
        public link(int max) {
            if (max > 0) {
                this.max = max;
            } else {
                throw new InvalidParameterException("參數不能小於0");
            }
        }

        // 最大能添加的元素的個數
        int max;
        // 鏈表中實際已經存儲的元素的個數
        int size;
        // 鏈表的頭節點
        Node first;
        // 鏈表的尾節點
        Node last;

        // 鏈表中的每個節點
        @AllArgsConstructor
        class Node {
            // 節點中保存的數據
            Object item;
            // 指向上一個節點
            Node pre;
            // 指向下一個節點
            Node next;
        }

        public void add(Object item) {
            Node node = new Node(item, null, null);
            if (size == 0) {
                size++;
                node.pre = null;
                node.next = null;
                first = node;
                last = node;
            } else {
                if (size == max) {
                    // 鏈表中的數據已經滿了
                    // 策略1:先進先出,需要刪除頭節點
                    
                    Node second = first.next;
                    second.pre = null;
                    first.next = null;
                    first = second;
                    size--;
                }
                // 將數據添加到鏈表尾部
                size++;
                last.next = node;
                node.pre = last;
                last = node;
            }
        }

        public void sout() {
            Node node = first;
            for (int i = 0; i < max; i++) {
                if (size == 0) {
                    System.out.println("鏈表中沒有元素");
                    break;
                } else {
                    System.out.println(node.item);
                    node = node.next;
                    if (node == null) {
                        System.out.println("遍歷結束");
                        break;
                    }
                }
            }
        }
    }

}

輸出結果如下:

4
5
6
7
最少使用策略(LFU,Least Frequently Out)

根據數據的歷史訪問頻率來淘汰數據,其核心思想是“如果數據過去被訪問多次,那麼將來被訪問的頻率也更高”。

下面給出兩種實現方式

JDK自帶優先級隊列進行實現

實現思路:利用PriorityQueue小頂堆的特點,關於PriorityQueue可參考我之前的文章 java讀源碼 之 queue源碼分析(PriorityQueue,附圖)。每次出隊的一定是優先級最低的元素,同時我們自定義比較器,我這裏是通過用實體實現Comparable接口的方式。

創建類如下:

package com.study.spring.transaction.huancun;

import java.time.LocalDateTime;

/**
 * 進行一個簡單的抽象,所有實體都要通過訪問時間跟訪問次數進行比較
 * @author dmz
 * @date Create in 23:45 2019/8/10
 */
public interface Count extends Comparable<Count> {

    void setLastVisitTime(LocalDateTime localDateTime);

    LocalDateTime getLastVisitTime();

    int getCount();

    void setCount(int count);

    default void addCount() {
        setCount(getCount() + 1);
    }

    // 根據訪問次數比較,次數相同的情況下比較訪問時間
    @Override
    default int compareTo(Count o2) {
        return this.getCount() == o2.getCount() ? this.getLastVisitTime().compareTo(o2.getLastVisitTime())
                : this.getCount() - o2.getCount();
    }
}

package com.study.spring.transaction.huancun;

import lombok.Data;

import java.time.LocalDateTime;

/**
 * @author dmz
 * @date Create in 23:45 2019/8/10
 */
@Data
public class Entity implements Count, Comparable<Entity> {

    private int count;

    private Object object;

    private LocalDateTime lastVisitTime = LocalDateTime.now();

    Entity(Object object) {
        this.object = object;
    }	
}

package com.study.spring.transaction.huancun;

import java.time.LocalDateTime;
import java.util.PriorityQueue;

/**
 * @author dmz
 * @date Create in 23:55 2019/8/10
 */
public class LFUStrategy<T extends Count> {

    private int max;

    LFUStrategy(PriorityQueue<T> priorityQueue, int max) {
        this.max = max;
        this.priorityQueue = priorityQueue;
    }

    private PriorityQueue<T> priorityQueue;

    /**
     * 訪問排列在index位置的元素
     *
     * @param index 位置
     * @return 所在元素
     */
    public T get(int index) {
        int count = 1;
        for (T t : priorityQueue) {
            if (count == index) {
                priorityQueue.remove(t);
                t.addCount();
                t.setLastVisitTime(LocalDateTime.now());
                // 先remove再add是爲了利用其內部特性,調用add方法時會進行排序,將最小的元素置於堆頂
                priorityQueue.add(t);
                return t;
            }
        }
        return null;
    }

    @Override
    public String toString() {
        return "LFUStrategy{" +
                "priorityQueue=" + priorityQueue +
                '}';
    }

    public T remove() {
        return priorityQueue.poll();
    }

    public void add(T t) {
        if (max == priorityQueue.size()) {
            T remove = remove();
            System.out.println("執行LFU策略,刪除的元素是:" + remove);
        } else {
            priorityQueue.offer(t);
        }
    }
}
package com.study.spring.transaction.huancun;

import java.util.PriorityQueue;

/**
 * @author dmz
 * @date Create in 0:08 2019/8/11
 */
public class Main {
    public static void main(String[] args) {
        PriorityQueue<Entity> priorityQueue = new PriorityQueue<>();
        for (int i = 1; i < 7; i++) {
            Entity entity = new Entity(i);
            priorityQueue.add(entity);
            try {
                // 爲了將訪問時間隔開
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 最大容忍6個元素,元素滿了後,執行LFU策略
        LFUStrategy lfuStrategy = new LFUStrategy<>(priorityQueue, 6);
        // 對第一個元素進行訪問
//        lfuStrategy.get(1);
        lfuStrategy.add(new Entity(7));
    }
}

在上述代碼中,如果我們不訪問任何元素,直接執行 lfuStrategy.add(new Entity(7)),那麼就會直接移除第一個元素,但是如果我們訪問第一個元素,那麼就會移除第二個元素。大家可以自己試驗,我就不貼運行結果了,太佔篇幅

鏈表實現

實現思路:相比於上一種實現方式,這裏最大的不同就是要自己去實現排序的過程。我們還是直接用JDK中的LinkedList,進行實現。

package com.study.spring.transaction.huancun;

import lombok.Data;
import lombok.RequiredArgsConstructor;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.LinkedList;

/**
 * @author dmz
 * @date Create in 12:03 2019/8/11
 */
@Data
@AllArgsConstructor
public class LFUStrategySecond<T extends Count> {
    private int max;
    private LinkedList<T> linkedList;

    public void add(T t) {
        if (linkedList.size() == max) {
            System.out.println("鏈表實現LFU策略刪除元素:" + remove());
        }
        linkedList.add(t);
    }

    public T remove() {
        return linkedList.removeFirst();
    }

    public T get(int index) {
        T t = linkedList.get(index);
        t.addCount();
        t.setLastVisitTime(LocalDateTime.now());
        // 最小的放到鏈表頭
        linkedList.sort((Comparator<T>) (o1, o2) ->
                o1.getCount() == o2.getCount() ? o1.getLastVisitTime().compareTo(o2.getLastVisitTime())
                        : o1.getCount() - o2.getCount()
        );
        return t;
    }
}


ackage com.study.spring.transaction.huancun;

import java.util.LinkedList;

/**
 * @author dmz
 * @date Create in 12:10 2019/8/11
 */
public class MainSecond {
    public static void main(String[] args) {

        LinkedList<Entity> linkedList = new LinkedList<>();
        linkedList.add(new Entity(1));
        linkedList.add(new Entity(2));
        linkedList.add(new Entity(3));
        linkedList.add(new Entity(4));
        linkedList.add(new Entity(5));
        LFUStrategySecond<Entity> lfuStrategySecond = new LFUStrategySecond<Entity>(5, linkedList);
        // 可以觀察,進行訪問跟不進行訪問,刪除元素的區別
  //      lfuStrategySecond.get(0);
        lfuStrategySecond.add(new Entity(6));
    }
}
最近最少使用策略(LRU,Least Recently Used)

根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是“如果數據最近被訪問過,那麼將來被訪問的機率也更高”。

實現思路:我們還是直接利用JDK的Linkedlist來進行實現,只要每次將被訪問的元素置於鏈尾,若空間不夠堆鏈頭的數據進行刪除即可。

package com.study.spring.transaction.huancun;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.LinkedList;

/**
 * @author dmz
 * @date Create in 12:25 2019/8/11
 */
@Data
@AllArgsConstructor
public class LRUStrategy<T> {
    // 不能小於1
    int max;

    LinkedList<T> linkedList = new LinkedList<>();

    public T get(int index) {
        // 每次獲取元素時,將獲取到的元素放到鏈尾
        T t = linkedList.get(index);
        linkedList.remove(t);
        linkedList.addLast(t);
        return t;
    }

    public T remove() {
        return linkedList.removeFirst();
    }

    public void add(T t) {
        if (linkedList.size() == max) {
            System.out.println("LRU刪除的元素:"+remove());
        }
        linkedList.add(t);
    }
}

總結:

到這裏三種緩存過期策略我們就介紹完了,可能對於最後一種LRU算法介紹的不是很詳細,本來是想自定義一個單鏈表來實現的,不過這兩天寫鏈表都寫吐了,實在不想寫了,偷了個懶,大家可以自己實現下,有任何問題都可以給我留言哦,一定會回覆的!

碼字不易,喜歡的小夥伴點個贊,加個關注吧,萬分感謝,你的支持就是我寫作的最大動力!

數據結構與算法|目錄彙總

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章