Java中的LRU缓存实现
时间:2020-02-23 14:37:20 来源:igfitidea点击:
什么是LRU缓存?
LRU缓存代表"最近最少使用的缓存"。
缓存的大小是固定的,并且支持get()和put()操作。
当缓存已满时,put()操作将删除最近最少使用的缓存。
如何在Java中实现LRU Cache?
LRU缓存可以使用两种数据结构在Java中实现-HashMap和用于存储数据的双向链接列表。
想法是始终按以下顺序排列元素。
最近使用最少(LRU)-> A <-> B <-> C <-> D <-> E <-最近使用(MRU)
这是Java中的LRUCache实现。
package com.theitroad.examples; import java.util.HashMap; import java.util.Map; public class LRUCache<K, V> { private Node<K, V> lru; private Node<K, V> mru; private Map<K, Node<K, V>> container; private int capacity; private int currentSize; public LRUCache(int capacity) { this.capacity = capacity; this.currentSize = 0; lru = new Node<K, V>(null, null, null, null); mru = lru; container = new HashMap<K, Node<K, V>>(); } public V get(K key) { Node<K, V> tempNode = container.get(key); if (tempNode == null) { return null; } //If MRU leave the list as it is else if (tempNode.key == mru.key) { return mru.value; } //Get the next and prev nodes Node<K, V> nextNode = tempNode.next; Node<K, V> prevNode = tempNode.prev; //If at the left-most, we update LRU if (tempNode.key == lru.key) { nextNode.prev = null; lru = nextNode; } //If we are in the middle, we need to update the items before and after our //item else if (tempNode.key != mru.key) { prevNode.next = nextNode; nextNode.prev = prevNode; } //Finally move our item to the MRU tempNode.prev = mru; mru.next = tempNode; mru = tempNode; mru.next = null; return tempNode.value; } public void put(K key, V value) { if (container.containsKey(key)) { return; } //Put the new node at the right-most end of the linked-list Node<K, V> myNode = new Node<K, V>(mru, null, key, value); mru.next = myNode; container.put(key, myNode); mru = myNode; //Delete the left-most entry and update the LRU pointer if (currentSize == capacity) { container.remove(lru.key); lru = lru.next; lru.prev = null; } //Update container size, for the first added entry update the LRU pointer else if (currentSize < capacity) { if (currentSize == 0) { lru = myNode; } currentSize++; } } //Node for doubly linked list class Node<T, U> { T key; U value; Node<T, U> prev; Node<T, U> next; public Node(Node<T, U> prev, Node<T, U> next, T key, U value) { this.prev = prev; this.next = next; this.key = key; this.value = value; } } }
有关实施的一些重要点。
Node类用于实现双向链表结构。
它具有对上一个和下一个节点的引用。当我们使用get()方法获取值时,该元素将移动到最右边的位置,因为它成为MRU元素。
当我们将元素放入缓存时,它会添加到最右侧。
如果容器已满,则将删除LRU元素。我们正在使用泛型,以便我们可以将实现与任何类型的数据一起使用。
LRUCache测试程序
我正在使用JUnit测试用例来运行LeetCode问题中的某些方案。
package com.theitroad.examples; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class LRUCacheTest { private LRUCache<Integer, Integer> c; public LRUCacheTest() { this.c = new LRUCache<>(2); } @Test public void testCacheStartsEmpty() { assertEquals(c.get(1), null); } @Test public void testSetBelowCapacity() { c.put(1, 1); assertEquals(c.get(1), 1); assertEquals(c.get(2), null); c.put(2, 4); assertEquals(c.get(1), 1); assertEquals(c.get(2), 4); } @Test public void testCapacityReachedOldestRemoved() { c.put(1, 1); c.put(2, 4); c.put(3, 9); assertEquals(c.get(1), null); assertEquals(c.get(2), 4); assertEquals(c.get(3), 9); } @Test public void testGetRenewsEntry() { c.put(1, 1); c.put(2, 4); assertEquals(c.get(1), 1); c.put(3, 9); assertEquals(c.get(1), 1); assertEquals(c.get(2), null); assertEquals(c.get(3), 9); } }