引至【想不到!面试官问我: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 }