1. 程式人生 > 其它 >Leetcode 146. LRU 快取機制

Leetcode 146. LRU 快取機制

前言

快取是一種提高資料讀取效能的技術,在計算機中cpu和主記憶體之間讀取資料存在差異,CPU和主記憶體之間有CPU快取,而且在記憶體和硬碟有記憶體快取。當主存容量遠大於CPU快取,或磁碟容量遠大於主存時,哪些資料應該被應該被清理,哪些資料應該被保留,這就需要快取淘汰策略來決定。常見的策略有三種:先進先出策略FIFO(First In,First Out)、最少使用策略LFU(Least Frequently Used)、最近最少使用策略LRU(Least Recently Used)。

LRU描述

設計和實現一個 LRU (最近最少使用) 快取機制 。
實現 LRUCache 類:

  • LRUCache(int capacity) 以正整數作為容量capacity 初始化 LRU 快取
  • int get(int key) 如果關鍵字 key 存在於快取中,則返回關鍵字的值,否則返回 -1 。
  • void put(int key, int value)如果關鍵字已經存在,則變更其資料值;如果關鍵字不存在,則插入該組「關鍵字-值」。當快取容量達到上限時,它應該在寫入新資料之前刪除最久未使用的資料值,從而為新的資料值留出空間。

解題思路 雜湊表 + 雙向連結串列

  • 針對LRU的特點,選擇使用雙鏈表實現。
  • 使用 gut 方法獲取資料,如果有資料,把返回資料,並且把資料放在連結串列頭部。
  • 使用 put 方法存放資料,如果資料存在,直接覆蓋新值;如果資料不存在,新增新值。新值都放在連結串列頭部。此外,還需要判斷快取有沒有超出容量 capacity,如果有超出,刪除連結串列的尾結點。
  • 因為是單鏈表,每次獲取資料,或者刪除資料,都需要遍歷一遍連結串列,時間複雜度是O(n),這裡使用hash來記錄每個資料的位置,將資料訪問的時間複雜度降到O(1)。
class LRUCache {

    class DLinkedNode{
		int key;
		int value;
		DLinkedNode prev;
		DLinkedNode next;

		public DLinkedNode() {}

		public DLinkedNode(int key, int value) {
			this.key = key;
			this.value = value;
		}
	}

	private int size;

	private int capacity;

	private DLinkedNode head;

	private DLinkedNode tail;

	private Map<Integer,DLinkedNode> cache = new HashMap<>();

    public LRUCache(int capacity) {
        this.size = 0;
		this.capacity = capacity;
        head = new DLinkedNode();
		tail = new DLinkedNode();
		head.next = tail;
		tail.prev = head;
    }
    
    public int get(int key) {
        DLinkedNode node = cache.get(key);
		if (node == null) {
			return -1;
		}
		//找到並移動到首位
		moveToHead(node);
		return node.value;

    }
    
    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
		if (node == null) {
			//不存在就建立一個新的節點
			DLinkedNode newNode = new DLinkedNode(key,value);
			cache.put(key,newNode);
			addToHead(newNode);
			size++;
			if (size > capacity) {
				//超出容量,移除最後節點
				DLinkedNode tail = removeTail();
				cache.remove(tail.key);
				size--;
			}
		} else {
			//key存在,覆蓋value,並移到頭部
			if (node.value != value) {
				node.value = value;
			}
			moveToHead(node);

		}
    }

    private DLinkedNode removeTail() {
		DLinkedNode node = tail.prev;
		removeNode(node);
		return node;
	}

	private DLinkedNode removeNode(DLinkedNode node) {
		node.next.prev = node.prev;
		node.prev.next = node.next;
		return node;
	}

	private void moveToHead(DLinkedNode node) {
		removeNode(node);
		addToHead(node);
	}

	private void addToHead(DLinkedNode node) {
		node.prev = head;
		node.next = head.next;
        head.next.prev = node;
		head.next = node;
	}
}

參考

LRU維基百科
極客時間-王爭-如何實現LRU快取淘汰演算法?