高薪必備:如何實現帶有過期時間的LRU?(java版)

在很早之前學操作系統的時候見過這個算法,後來見到的越來越多,以至於刷面經的時候也看到了,總結一下:

一、什麼是LRU

LRU全稱是Least Recently Used,即最近最久未使用的意思。也就是說:如果一個數據在最近一段時間沒有被使用,將來被使用的機會也比較小。

通常的使用場景就是緩存,比如說操作系統中的頁面置換算法。實現的方案有很多,我看了很多博客,大多是給了四五種。這裏爲了簡潔,只給出一種,是帶有過期時間的。其他的實現類似,就交給聰明的你吧!!

解決方案:利用鏈表加HashMap

在這裏插入圖片描述
每次來一個新數據,首先判斷map中是否含有,有的話就移動到隊頭,沒有的話就新建一個節點,然後放進來就好,對於帶過期時間的功能,只需要爲每一個節點放一個過期時間,只要到了這個時間就直接刪除即可。

還有一個問題:多線程環境下應該加鎖,爲了保證鎖的靈活性,我們使用ConcurrentHashMap。

OK,下面我們就開始實現:

二、代碼實現

1、定義節點

//這個Node對用HashMap中每一個節點
class Node implements Comparable<Node> {
    private String key;
    private Object value;
    private long expireTime;//注意這個過期時間是一個時間點,如11點11分
    public Node(String key, Object value, long expireTime) {
        this.value = value;
        this.key = key;
        this.expireTime = expireTime;
    }
    //按照過期時間進行排序
    @Override
    public int compareTo(Node o) {
        long r = this.expireTime - o.expireTime;
        if (r > 0)  return 1;
        if (r < 0) return -1;
        return 0;
    }
}

2、LRU實現

public class LRU {
    // 變量1:用於設置清除過期數據的線程池
	private static ScheduledExecutorService swapExpiredPool 
		          = new ScheduledThreadPoolExecutor(10);
	// 變量2:用戶存儲數據,爲了保證線程安全,使用了ConcurrentHashMap
	private ConcurrentHashMap<String, Node> cache = new ConcurrentHashMap<>(1024);
	// 變量3:保存最新的過期數據,過期時間最小的數據排在隊列前
	private PriorityQueue<Node> expireQueue = new PriorityQueue<>(1024);
	// 構造方法:只要有緩存了,過期清除線程就開始工作
	public LRU() {
		swapExpiredPool.scheduleWithFixedDelay(new ExpiredNode(), 3,3,TimeUnit.SECONDS);
	}
    //還有代碼。。。。。。。
}

現在我們定義了幾個變量,然後還有一個構造方法,意思是隻要啓動了這個LRU,就開始清除。清除的線程是ExpiredNode。我們來看一下:

3、過期清除線程方法

這個方法也就是ExpiredNode,當做一個內部類在LRU中。

	public class ExpiredNode implements Runnable {
		@Override
		public void run() {
			// 第一步:獲取當前的時間
			long now = System.currentTimeMillis();
			while (true) {
				// 第二步:從過期隊列彈出隊首元素,如果不存在,或者不過期就返回
				Node node = expireQueue.peek();
				if (node == null || node.expireTime > now)return;
				// 第三步:過期了那就從緩存中刪除,並且還要從隊列彈出
				cache.remove(node.key);
				expireQueue.poll();
			}// 此過程爲while(true),一直進行判斷和刪除操作
		}
	}

現在知道了過期清除方法,下面看看如何添加數據。

4、set方法

  	public Object set(String key, Object value, long ttl) {
		// 第一步:獲取過期時間點
		long expireTime = System.currentTimeMillis() + ttl;
		// 第二步:新建一個節點
		Node newNode = new Node(key, value, expireTime);
		// 第三步:cache中有的話就覆蓋,沒有就添加新的,過期時間隊列也要添加
		Node old = cache.put(key, newNode);
		expireQueue.add(newNode);
		// 第四步:如果該key存在數據,還要從過期時間隊列刪除
		if (old != null) {
			expireQueue.remove(old);
			return old.value;
		}
		return null;
	}

5、get方法

這個方法就比較簡單了,直接獲取即可。

public Object get(String key) {
    //第一步:從cache直接獲取,注意這個cache是一個HashMap
    Node n = cache.get(key);
    //第二步:如果n爲空那就返回爲null,不爲空就返回相應的值
    return n == null ? null : n.value;
}

注意以上345的代碼都存放在LRU中。

過期時間的我們已經知道了,其實就是添加了一個過期時間隊列,和一個過期清除的線程,清除的時候使用while(true)每次判斷隊列隊首是否過期,然後判斷是否返回和清除。設置方法的時候還要把新的node添加到queue,把舊的移除掉。而且我們使用了ConcurrentHashMap保證了線程安全。

OK,今天的代碼就先寫到這。

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