LRU缓存
1.简述LRU
这里使用一张图进行描述
示例使用的 capicity
为 3,进入顺序为 7 0 1 2 0 3 0 4
2. 实现LRU
设计需求:
-
获取数据
get(key)
: 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回-1
。 -
写入数据
put(key, value)
: 如果密钥已经存在,则变更其数据值;如果密钥不存在,则插入该组「密钥/数据值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 -
时间复杂度:O(1)
此处考虑:双向链表+哈希Map的结构来实现这个结构。
理由:双向链表可以O(1)进行链表的插入删除操作,HashMap可以O(1)时间查找到对应节点!
队列结构如下:
3. 实现代码
-
节点的定义:
public static class Node { int key; int value; Node last; Node next; public Node(int key, int value){ this.key = key; this.value = value; last = null; next = null; } }
-
双向列表定义:
这里双向链表的具体断链操作不进行阐述
public static class LRULinkedList{ private Node head; private Node tail; private int size ; public LRULinkedList(){ head = null; tail = null; this.size = 0; } // 双向链表的插入节点 public void add(Node node){ if (head == null){ head = node; tail = node; }else if (head == tail){ tail.last = node; node.last = null; node.next = head; head = node; }else{ node.last = null; node.next = head; head.last = node; head = node; } size++; } // 如果队列达到最大capicity,则进行删除尾节点操作 public Node removeTail(){ Node node = tail; if (tail == head){ head = null; tail = null; return node; } tail = tail.last; tail.next.last = null; tail.next = null; size--; return node; } // 更新节点的最近访问记录 public void moveToHead(Node node){ if (node == head){ return; } if (node == tail){ node.last.next = null; tail = node.last; }else{ node.last.next = node.next; node.next.last = node.last; } node.next = head; head.last = node; head = node; node.last = null; } public int size(){ return size; } }
-
LRU实现
// 最大容量 int capicity; // 用于快速定位节点 HashMap<Integer, Node> keyNodeMap; HashMap<Node, Integer> nodeKeyMap; // 双向链表 LRULinkedList list; public LRUCache(int capacity) { this.list = new LRULinkedList(); this.capicity = capacity; keyNodeMap = new HashMap<>(); nodeKeyMap = new HashMap<>(); } public int get(int key) { Node node; // 判断队列中是否有key对应的node if ((node = keyNodeMap.get(key)) != null){ list.moveToHead(node); }else{ return -1; } return node.value; } // 插入操作 public void put(int key, int value) { int size = list.size(); Node node; if ((node = keyNodeMap.get(key)) == null){ // 超过容量 进行尾部删除 if (size == capicity){ node = list.removeTail(); int rkey = nodeKeyMap.get(node); nodeKeyMap.remove(node); keyNodeMap.remove(rkey); } // 插入节点 node = new Node(key,value); keyNodeMap.put(key,node); nodeKeyMap.put(node,key); list.add(node); }else{ // 此处只需要进行节点value修改 node.value = value; // 调整节点的位置 list.moveToHead(node); } }
-
测试用例
@Test public void main() { LRUCache cache = new LRUCache( 2 /* 缓存容量 */ ); cache.put(2, 6); cache.put(1, 5); System.out.println(cache.get(1)); cache.put(1, 2); // 该操作会使得密钥 2 作废 System.out.println(cache.get(2)); } /* 5 6 */