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

用戶系統作為非常基礎的應用,公司內部會有很多個系統去調用,因此一定要注意性能問題。因此在用戶系統中,可以增加一個內存緩存,當然具體的信息是存放在數據庫里的。每當查找一個用戶時會先在哈希表中進行查詢,以此來提高訪問的性能。
不過把緩存數據放在內存也會導致一個問題,就是時間長了之后,內存可能會由於數據太多而溢出,那么這時候就需要有緩存清除機制來保證系統正常運行。
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")); } }