LRU算法與代碼實現


場景

公司的業務越來越復雜,我們需要抽出一個用戶系統,向各個業務系統提供用戶的基本信息。

 

用戶系統作為非常基礎的應用,公司內部會有很多個系統去調用,因此一定要注意性能問題。因此在用戶系統中,可以增加一個內存緩存,當然具體的信息是存放在數據庫里的。每當查找一個用戶時會先在哈希表中進行查詢,以此來提高訪問的性能。

 

不過把緩存數據放在內存也會導致一個問題,就是時間長了之后,內存可能會由於數據太多而溢出,那么這時候就需要有緩存清除機制來保證系統正常運行。

LRU算法

LRU全稱Least Recently Used,也就是最近最少使用的意思,是一種內存管理算法,該算法最早應用於Linux操作系統。
這個算法基於一種假設:長期不被使用的數據,在未來被用到的幾率也不大。因此,當數據所占內存達到一定閾值時,我們要移除掉最近最少被使用的數據。
在LRU算法中,使用了一種有趣的數據結構,這種數據結構叫作哈希鏈表。

哈希鏈表

哈希表是由若干個Key-Value組成的。在“邏輯”上,這些Key-Value是無所謂排列順序的,誰先誰后都一樣。

在哈希鏈表中,這些Key-Value不再是彼此無關的存在,而是被一個鏈條串了起來。每一個Key-Value都具有它的前驅Key-Value、后繼Key-Value,就像雙向鏈表中的節點一樣。

這樣一來,原本無序的哈希表就擁有了固定的排列順序。

LRU算法演示

1. 假設使用哈希鏈表來緩存用戶信息,目前緩存了4個用戶,這4個用戶是按照被訪問的時間順序依次從鏈表右端插入的。

2. 如果這時業務方訪問用戶5,由於哈希鏈表中沒有用戶5的數據,需要從數據庫中讀取出來,插入到緩存中。此時,鏈表最右端是最新被訪問的用戶5,最左端是最近最少被訪問的用戶1。

3. 接下來,如果業務方訪問用戶2,哈希鏈表中已經存在用戶2的數據,這時我們把用戶2從它的前驅節點和后繼節點之間移除,重新插入鏈表的最右端。此時,鏈表的最右端變成了最新被訪問的用戶2,最左端仍然是最近最少被訪問的用戶1。

4. 接下來,如果業務方請求修改用戶4的信息。同樣的道理,我們會把用戶4從原來的位置移動到鏈表的最右側,並把用戶信息的值更新。這時,鏈表的最右端是最新被訪問的用戶4,最左端仍然是最近最少被訪問的用戶1。

5. 后來業務方又要訪問用戶6,用戶6在緩存里沒有,需要插入哈希鏈表中。假設這時緩存容量已經達到上限,必須先刪除最近最少被訪問的數據,那么位於哈希鏈表最左端的用戶1就會被刪除,然后再把用戶6插入最右端的位置。

代碼實現

public class LRUCache {
    private Node head;

    private Node end;

    // 緩存存儲上限
    private int limit;

    private HashMap<String, Node> hashMap;

    public LRUCache(int limit) {
        this.limit = limit;
        hashMap = new HashMap<String, Node>();
    }

    public String get(String key) {
        Node node = hashMap.get(key);
        if(node == null) {
            return null;
        }

        refreshNode(node);
        return node.value;
    }

    public void put(String key, String value) {
        Node node = hashMap.get(key);
        if(node == null) {
            // 如果key不存在,插入key-value
            if(hashMap.size() >= limit) {
                String oldKey = removeNode(head);
                hashMap.remove(oldKey);
            }

            node = new Node(key, value);
            addNode(node);
            hashMap.put(key, node);
        } else {
            // 如果key存在,刷新key-value
            node.value = value;
            refreshNode(node);
        }
    }

    public void remove(String key) {
        Node node = hashMap.get(key);
        removeNode(node);
        hashMap.remove(key);
    }

    /**
     * 
     * 刷新被訪問的節點位置
     * 
     * @param node 被訪問的節點
     * 
     */
    private void refreshNode(Node node) {
        // 如果訪問的是尾節點,無需移動節點
        if(node == end) {
            return;
        }
        // 移除節點
        removeNode(node);
        // 重新插入節點
        addNode(node);
    }

    /**
     * 
     * 刪除節點
     * 
     * @param node 要刪除的節點
     * 
     */
    private String removeNode(Node node) {
        if(node == end) {
            // 移除尾節點
            end = end.pre;
        }else if(node == head) {
            // 移除頭節點
            head = head.next;
        }else {
            // 移除中間節點
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }

        return node.key;
    }

    /**
     * 
     * 尾部插入節點
     * 
     * @param node 要插入的節點
     * 
     */
    private void addNode(Node node) {
        if (end != null) {
            end.next = node;
            node.pre = end;
            node.next = null;
        }

        end = node;

        if (head == null) {
            head = node;
        }
    }

    class Node {
        Node(String key, String value) {
            this.key = key;
            this.value = value;
        }
        public Node pre;
        public Node next;
        public String key;
        public String value;
    }

    public static void main(String[] args) {
        LRUCache lruCache = new LRUCache(5);
        lruCache.put("001", "用戶1信息");
        lruCache.put("002", "用戶1信息");
        lruCache.put("003", "用戶1信息");
        lruCache.put("004", "用戶1信息");
        lruCache.put("005", "用戶1信息");
        lruCache.get("002");
        lruCache.put("004", "用戶2信息更新");
        lruCache.put("006", "用戶6信息");
        System.out.println(lruCache.get("001"));
        System.out.println(lruCache.get("006"));
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM