需求
隨着公司的業務越來越復雜,需要提供一個用戶系統,供各個業務系統來查詢用戶的基本信息。且業務方對用戶信息的查詢頻率很高,設計的用戶系統需要注意性能。
- 初始設計: 考慮到性能,可以在內存中創建一個哈希表作為緩存,每當查找一個用戶時,會現在哈希表中進行查詢,查詢不到再去數據庫查詢。
- 初始設計存在的問題: 隨着用戶量不斷增大,可能會因為哈希表逐漸增大導致內存某一天會被撐爆。
- 初始設計的優化:使用LRU算法(內存管理算法,Least Recently Used),最近最少使用的思想。算法基於一種假設:長期不被使用的數據,在未來被用到的幾率也不大。因此,當數據所占內存達到一定閾值時,我們要移除掉最近最少被使用的數據。
LRU算法
在LRU算法中,使用一種叫作“哈希鏈表”的數據結構。哈希表由若干個key-value組成,邏輯上這些key-value是無所謂排序的。但在哈希鏈表中,這些key-value被一個鏈條串在一起,不再是無序的了,而是有 了固定的排列順序。每一個key-value都有前驅key-value、后繼key-value。所以我們可以把key-value按照最后使用的時間來進行排序。鏈表的尾部是最近被使用的,頭部是最近被使用最少甚至沒有被使用過的key-value。所以,當緩存容量達到上限時,會先刪除鏈表最左端的值,再把新的值插入到鏈表的最右端。
LRU算法實現
哈希鏈表的結構:

注意點: 刪除節點的同時,記得把節點的key也要從hashMap中刪除
1 package blogSrc; 2 3 import java.util.HashMap; 4 5 public class LRUCache { 6 private Node head; 7 private Node end; 8 9 private HashMap<String,Node> hashMap; 10 private int limit; //緩存上限 11 12 public LRUCache(int limit){ 13 this.limit = limit; 14 this.hashMap = new HashMap<String,Node>(); 15 } 16 17 //向鏈表中添加節點 18 public void addNode (Node node){ 19 if (head == null){ //空鏈 20 head = node; 21 } 22 23 if(end != null) { //節點添加到尾部 24 end.next = node; 25 node.pre = end; 26 node.next = null; 27 } 28 end = node; 29 } 30 31 //從鏈表中移除節點 32 public String removeNode(Node node){ 33 if(head == end && node == head){ //鏈表只有一個節點 34 head = null; 35 end = null; 36 }else if (node == end){ //node為最后一個節點 37 end.pre.next = null; 38 end = end.pre; 39 }else if (node == head){ //node為第一個節點 40 head.next.pre = null; 41 head = head.next; 42 }else { //node為中間節點 43 node.pre.next = node.next; 44 node.next.pre = node.pre; 45 } 46 return node.key; 47 } 48 49 //刷新鏈表,將最近使用的節點放置到鏈表末尾 50 public void refreshNode(Node node){ 51 if (node == end){ 52 return; 53 } 54 removeNode(node); //刪除節點 55 addNode(node); //添加節點 56 } 57 58 //根據節點的key值,獲取鏈表中該節點的value值 59 public String get(String key){ 60 Node node = hashMap.get(key); 61 if (node == null){ 62 return null; 63 } 64 refreshNode(node); 65 return node.value; 66 } 67 68 //向鏈表上添加key-value 69 public void put(String key, String value){ 70 Node node = hashMap.get(key); 71 if (node == null){ 72 if (hashMap.size() >= limit){ 73 String oldKey = removeNode(head); //刪除節點 74 hashMap.remove(oldKey); //同時需要把節點的key也從hashmap中刪除 75 } 76 node = new Node(key,value); 77 addNode(node); 78 hashMap.put(key,node); 79 }else { 80 node.value = value; 81 refreshNode(node); 82 } 83 } 84 85 //從鏈表上刪除指定key的數據 86 public void remove(String key){ 87 Node node = hashMap.get(key); 88 if (node == null){ 89 return ; 90 } 91 removeNode(node); 92 hashMap.remove(key); 93 } 94 95 class Node { 96 public Node next; 97 public Node pre; 98 public String key; 99 public String value; 100 Node(String key, String value){ 101 this.key = key; 102 this.value = value; 103 } 104 105 public String getNextKey() { 106 return next.getKey(); 107 } 108 109 public String getPreKey() { 110 return pre.getKey(); 111 } 112 113 public String getKey() { 114 return key; 115 } 116 117 public String getValue() { 118 return value; 119 } 120 } 121 122 123 public static void main(String[] args){ 124 LRUCache lruCache = new LRUCache(10); 125 lruCache.put("001","用戶1信息"); 126 lruCache.put("002","用戶2信息"); 127 lruCache.put("003","用戶3信息"); 128 lruCache.put("004","用戶4信息"); 129 lruCache.get("003"); 130 System.out.println("Now End Node Key is: " + lruCache.end.getKey()+ ",Value is: " +lruCache.end.getValue()); 131 lruCache.put("002","用戶2信息更新"); 132 System.out.println("Now End Node Key is: " + lruCache.end.getKey() + ",Value is: " +lruCache.end.getValue() ); 133 lruCache.put("006","用戶6信息"); 134 System.out.println("Now End Node Key is: " + lruCache.end.getKey()+ ",Value is: " +lruCache.end.getValue()); 135 } 136 }
結果:
Now End Node Key is: 003,Value is: 用戶3信息 Now End Node Key is: 002,Value is: 用戶2信息更新 Now End Node Key is: 006,Value is: 用戶6信息