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);
}
}

