LRU算法
引至【想不到!面试官问我:Redis 内存满了怎么办?】,本文只关注其中的LRU算法
LRU(Least Recently Used),即最近最少使用,是一种缓存置换算法。在使用内存作为缓存的时候,缓存的大小一般是固定的。当缓存被占满,这个时候继续往缓存里面添加数据,就需要淘汰一部分老的数据,释放内存空间用来存储新的数据。这个时候就可以使用LRU算法。其核心思想是:如果一个数据在最近一段时间没有被用到,那么将来被使用到的可能性也很小,所以就可以被淘汰掉。
使用java实现一个简单的LRU算法
主要思想:LRU内部实现一个Node<k,v>类,该类包含key、value,前一节点pre和后一节点next四个属性;LRU中包含一个Map<k, Node<k, v>> nodeMap的缓存map,包含所有的数据,哨兵 Node<k, v> head和 Node<k, v> tail标注链表的头部和尾部。当存入数据时(先判断LRU是否已超长,如果超长则需要删除数据)判断nodeMap是否包含该节点,如果不包含,则将该节点添加至nodeMap中,并将该节点插入至head后(即该节点pre设置为head,head节点的next设置为当前节点,该节点的next设置为head节点的的next);如果包含该数据,则需要将该node从原链表中删除,然后再将该数据插入至head后即可(将该节点的pre设置为head,head节点的next设置为当前节点)。当数据被get时,也需要先将该node从原链表中删除,然后再将该数据插入至head后(将该节点的pre设置为head,head节点的next设置为当前节点),即实现了LRU.
1 public class LRUCache<k, v> { 2 //容量 3 private int capacity; 4 //当前有多少节点的统计 5 private int count; 6 //缓存节点 7 private Map<k, Node<k, v>> nodeMap; 8 private Node<k, v> head; 9 private Node<k, v> tail; 10 11 public LRUCache(int capacity) { 12 if (capacity < 1) { 13 throw new IllegalArgumentException(String.valueOf(capacity)); 14 } 15 this.capacity = capacity; 16 this.nodeMap = new HashMap<>(); 17 //初始化头节点和尾节点,利用哨兵模式减少判断头结点和尾节点为空的代码 18 Node headNode = new Node(null, null); 19 Node tailNode = new Node(null, null); 20 headNode.next = tailNode; 21 tailNode.pre = headNode; 22 this.head = headNode; 23 this.tail = tailNode; 24 } 25 26 public void put(k key, v value) { 27 Node<k, v> node = nodeMap.get(key); 28 if (node == null) { 29 if (count >= capacity) { 30 //先移除一个节点 31 removeNode(); 32 } 33 node = new Node<>(key, value); 34 //添加节点 35 addNode(node); 36 } else { 37 //移动节点到头节点 38 moveNodeToHead(node); 39 } 40 } 41 42 public Node<k, v> get(k key) { 43 Node<k, v> node = nodeMap.get(key); 44 if (node != null) { 45 moveNodeToHead(node); 46 } 47 return node; 48 } 49 50 private void removeNode() { 51 Node node = tail.pre; 52 //从链表里面移除 53 removeFromList(node); 54 nodeMap.remove(node.key); 55 count--; 56 } 57 58 private void removeFromList(Node<k, v> node) { 59 Node pre = node.pre; 60 Node next = node.next; 61 62 pre.next = next; 63 next.pre = pre; 64 65 node.next = null; 66 node.pre = null; 67 } 68 69 private void addNode(Node<k, v> node) { 70 //添加节点到头部 71 addToHead(node); 72 nodeMap.put(node.key, node); 73 count++; 74 } 75 76 private void addToHead(Node<k, v> node) { 77 Node next = head.next; 78 next.pre = node; 79 node.next = next; 80 node.pre = head; 81 head.next = node; 82 } 83 84 public void moveNodeToHead(Node<k, v> node) { 85 //从链表里面移除 86 removeFromList(node); 87 //添加节点到头部 88 addToHead(node); 89 } 90 91 class Node<k, v> { 92 k key; 93 v value; 94 Node pre; 95 Node next; 96 97 public Node(k key, v value) { 98 this.key = key; 99 this.value = value; 100 } 101 } 102 }