一。LRU算法簡介
LRU(Least Recently Used)最近最久未使用算法
常見應用場景:內存管理中的頁面置換算法、緩存淘汰中的淘汰策略等
二。實現理論
底層結構:雙向鏈表 + HashMap ,雙向鏈表由特定的哈希節點組成。
(1)訪問節點時,將其從原來位置刪除,插入到雙向鏈表頭部;
(2)更新節點時,先刪除原有緩存數據(即原有節點),然后更新map映射,再將更新值作為節點插入鏈表頭;更新后,判斷容量是否超過最大內存使用量
(3)超過則執行淘汰;淘汰即刪除雙向鏈表最后一個節點,同時刪除map中的映射
(4)LRU實現中有頻繁的查找節點並刪除,為節省時間(鏈表查找目標節點需要遍歷),使用HashMap保存鍵-節點映射關系,O(1)的查找+O(1)的刪除
(5)LRU實現中,要頻繁的在頭部插入,以及在尾部刪除;因此,需要定義head、tail兩個節點,方便操作
三。代碼
1 package com.xl.Base; 2 3 import java.util.HashMap; 4 import java.util.Iterator; 5 6 /** 7 * 最近最久未使用淘汰策略 8 * 基於 雙向鏈表 + 哈希表組成,其中雙向鏈表由哈希鏈表節點構成 9 * 封裝為 LRU(K, V) 10 * 對外提供 get(K)訪問數據、put(K, V)更新數據、Iterator()遍歷數據 11 */ 12 public class LRU<K, V> implements Iterable<K>{ 13 14 private Node head; 15 private Node tail; 16 //記錄K-Node映射,便於快速查找目標數據對應節點 17 private HashMap<K, Node> map; 18 private int maxSize; 19 20 //哈希鏈表節點類 Node 21 private class Node{ 22 Node pre; 23 Node next; 24 K k; 25 V v; 26 27 //Node對外提供構造方法 28 public Node(K k, V v) { 29 this.k = k; 30 this.v = v; 31 } 32 } 33 34 //初始化時必須傳入最大可用內存容量 35 public LRU(int maxSize){ 36 this.maxSize = maxSize; 37 //HashMap初始容量設置為 maxSize * 4/3,即達到最大可用內存時,HashMap也不會自動擴容浪費空間 38 this.map = new HashMap<>(maxSize * 4 / 3); 39 40 head.next = tail; 41 tail.pre = head; 42 } 43 44 //獲取指定數據 45 private V get(K key) { 46 //判斷是否存在對應數據 47 if(!map.containsKey(key)) { 48 return null; 49 } 50 51 //最新訪問的數據移動到鏈表頭 52 Node node = map.get(key); 53 remove(node); 54 addFirst(node); 55 return node.v; 56 } 57 58 //更新舊數據或添加數據 59 private void put(K key, V value) { 60 //若存在舊數據則刪除 61 if(map.containsKey(key)) { 62 Node node = map.get(key); 63 remove(node); 64 } 65 66 //新數據對應節點插入鏈表頭 67 Node node = new Node(key, value); 68 map.put(key, node); 69 addFirst(node); 70 71 //判斷是否需要淘汰數據 72 if(map.size() > maxSize) { 73 removeLast(); 74 //數據節點淘汰后,同時刪除map中的映射 75 map.remove(node.k); 76 } 77 } 78 79 //將指定節點插入鏈表頭 80 private void addFirst(Node node) { 81 Node next = head.next; 82 83 head.next = node; 84 node.pre = head; 85 86 node.next = next; 87 next.pre = node; 88 } 89 90 //從鏈表中刪除指定節點 91 private void remove(Node node) { 92 Node pre = node.pre; 93 Node next = node.next; 94 95 pre.next = next; 96 next.pre = pre; 97 98 node.next = null; 99 node.pre = null; 100 } 101 102 //淘汰數據 103 private Node removeLast() { 104 //找到最近最久未使用的數據所對應節點 105 Node node = tail.pre; 106 107 //淘汰該節點 108 remove(node); 109 110 return node; 111 } 112 113 //通過迭代器遍歷所有數據對應鍵 114 @Override 115 public Iterator<K> iterator() { 116 return new Iterator<K>() { 117 118 private Node cur = head.next; 119 120 @Override 121 public boolean hasNext() { 122 return cur != tail; 123 } 124 125 @Override 126 public K next() { 127 Node node = cur; 128 cur = cur.next; 129 return node.k; 130 } 131 132 }; 133 } 134 135 }