BAEL-4464 : how to implement LRU-Cache in java codes added (#11036)
* BAEL-4464 : how to implement LRU-Cache in java codes added * BAEL-4464 : how to implement LRU-Cache in java codes added - package named fixed * BAEL-4464 : how to implement LRU-Cache in java codes added - package named changed * BAEL-4464 : how to implement LRU-Cache in java codes added - unitTest fixed
This commit is contained in:
parent
978bf7f543
commit
17814a1468
|
@ -0,0 +1,15 @@
|
||||||
|
package com.baeldung.lrucache;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface Cache<K, V> {
|
||||||
|
boolean put(K key, V value);
|
||||||
|
|
||||||
|
Optional<V> get(K key);
|
||||||
|
|
||||||
|
int size();
|
||||||
|
|
||||||
|
boolean isEmpty();
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.baeldung.lrucache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by arash on 09.07.21.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class CacheElement<K,V> {
|
||||||
|
private K key;
|
||||||
|
private V value;
|
||||||
|
|
||||||
|
public CacheElement(K key, V value) {
|
||||||
|
this.value = value;
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public K getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(K key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(V value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
package com.baeldung.lrucache;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
public class DoublyLinkedList<T> {
|
||||||
|
|
||||||
|
private DummyNode<T> dummyNode;
|
||||||
|
private LinkedListNode<T> head;
|
||||||
|
private LinkedListNode<T> tail;
|
||||||
|
private AtomicInteger size;
|
||||||
|
private ReentrantReadWriteLock.ReadLock readLock;
|
||||||
|
private ReentrantReadWriteLock.WriteLock writeLock;
|
||||||
|
|
||||||
|
|
||||||
|
public DoublyLinkedList() {
|
||||||
|
this.dummyNode = new DummyNode<T>(this);
|
||||||
|
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
|
readLock = lock.readLock();
|
||||||
|
writeLock = lock.writeLock();
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
|
head = dummyNode;
|
||||||
|
tail = dummyNode;
|
||||||
|
size = new AtomicInteger(0);
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
readLock.lock();
|
||||||
|
try {
|
||||||
|
return size.get();
|
||||||
|
} finally {
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
readLock.lock();
|
||||||
|
try {
|
||||||
|
return head.isEmpty();
|
||||||
|
} finally {
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(T value) {
|
||||||
|
readLock.lock();
|
||||||
|
try {
|
||||||
|
return search(value).hasElement();
|
||||||
|
} finally {
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedListNode<T> search(T value) {
|
||||||
|
readLock.lock();
|
||||||
|
try {
|
||||||
|
return head.search(value);
|
||||||
|
} finally {
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedListNode<T> add(T value) {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
|
head = new Node<T>(value, head, this);
|
||||||
|
if (tail.isEmpty()) {
|
||||||
|
tail = head;
|
||||||
|
}
|
||||||
|
size.incrementAndGet();
|
||||||
|
return head;
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addAll(Collection<T> values) {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
|
for (T value : values) {
|
||||||
|
if (add(value).isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedListNode<T> remove(T value) {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
|
LinkedListNode<T> linkedListNode = head.search(value);
|
||||||
|
if (!linkedListNode.isEmpty()) {
|
||||||
|
if (linkedListNode == tail) {
|
||||||
|
tail = tail.getPrev();
|
||||||
|
}
|
||||||
|
if (linkedListNode == head) {
|
||||||
|
head = head.getNext();
|
||||||
|
}
|
||||||
|
linkedListNode.detach();
|
||||||
|
size.decrementAndGet();
|
||||||
|
}
|
||||||
|
return linkedListNode;
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedListNode<T> removeTail() {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
|
LinkedListNode<T> oldTail = tail;
|
||||||
|
if (oldTail == head) {
|
||||||
|
tail = head = dummyNode;
|
||||||
|
} else {
|
||||||
|
tail = tail.getPrev();
|
||||||
|
oldTail.detach();
|
||||||
|
}
|
||||||
|
if (!oldTail.isEmpty()) {
|
||||||
|
size.decrementAndGet();
|
||||||
|
}
|
||||||
|
return oldTail;
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedListNode<T> moveToFront(LinkedListNode<T> node) {
|
||||||
|
return node.isEmpty() ? dummyNode : updateAndMoveToFront(node, node.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
public LinkedListNode<T> updateAndMoveToFront(LinkedListNode<T> node, T newValue) {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
|
if (node.isEmpty() || (this != (node.getListReference()))) {
|
||||||
|
return dummyNode;
|
||||||
|
}
|
||||||
|
detach(node);
|
||||||
|
add(newValue);
|
||||||
|
return head;
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void detach(LinkedListNode<T> node) {
|
||||||
|
if (node != tail) {
|
||||||
|
node.detach();
|
||||||
|
if (node == head) {
|
||||||
|
head = head.getNext();
|
||||||
|
}
|
||||||
|
size.decrementAndGet();
|
||||||
|
} else {
|
||||||
|
removeTail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.baeldung.lrucache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by arash on 09.07.21.
|
||||||
|
*/
|
||||||
|
public class DummyNode<T> implements LinkedListNode<T> {
|
||||||
|
private DoublyLinkedList<T> list;
|
||||||
|
|
||||||
|
public DummyNode(DoublyLinkedList<T> list) {
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasElement() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T getElement() throws NullPointerException {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void detach() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DoublyLinkedList<T> getListReference() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> setPrev(LinkedListNode<T> next) {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> setNext(LinkedListNode<T> prev) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> getPrev() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> getNext() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> search(T value) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package com.baeldung.lrucache;
|
||||||
|
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
public class LRUCache<K, V> implements Cache<K, V> {
|
||||||
|
private int size;
|
||||||
|
private Map<K, LinkedListNode<CacheElement<K, V>>> linkedListNodeMap;
|
||||||
|
private DoublyLinkedList<CacheElement<K, V>> doublyLinkedList;
|
||||||
|
private ReentrantReadWriteLock.ReadLock readLock;
|
||||||
|
private ReentrantReadWriteLock.WriteLock writeLock;
|
||||||
|
|
||||||
|
public LRUCache(int size) {
|
||||||
|
this.size = size;
|
||||||
|
this.linkedListNodeMap = new Hashtable<>(size);
|
||||||
|
this.doublyLinkedList = new DoublyLinkedList<>();
|
||||||
|
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||||
|
this.readLock = lock.readLock();
|
||||||
|
this.writeLock = lock.writeLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean put(K key, V value) {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
|
CacheElement<K, V> item = new CacheElement<K, V>(key, value);
|
||||||
|
LinkedListNode<CacheElement<K, V>> newNode;
|
||||||
|
if (this.linkedListNodeMap.containsKey(key)) {
|
||||||
|
LinkedListNode<CacheElement<K, V>> node = this.linkedListNodeMap.get(key);
|
||||||
|
newNode = doublyLinkedList.updateAndMoveToFront(node, item);
|
||||||
|
} else {
|
||||||
|
if (this.size() >= this.size) {
|
||||||
|
this.evictElement();
|
||||||
|
}
|
||||||
|
newNode = this.doublyLinkedList.add(item);
|
||||||
|
}
|
||||||
|
if (newNode.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.linkedListNodeMap.put(key, newNode);
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<V> get(K key) {
|
||||||
|
readLock.lock();
|
||||||
|
try {
|
||||||
|
LinkedListNode<CacheElement<K, V>> linkedListNode = this.linkedListNodeMap.get(key);
|
||||||
|
if (linkedListNode != null && !linkedListNode.isEmpty()) {
|
||||||
|
linkedListNodeMap.put(key, this.doublyLinkedList.moveToFront(linkedListNode));
|
||||||
|
return Optional.of(linkedListNode.getElement().getValue());
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
} finally {
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
readLock.lock();
|
||||||
|
try {
|
||||||
|
return doublyLinkedList.size();
|
||||||
|
} finally {
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
|
linkedListNodeMap.clear();
|
||||||
|
doublyLinkedList.clear();
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean evictElement() {
|
||||||
|
writeLock.lock();
|
||||||
|
try {
|
||||||
|
LinkedListNode<CacheElement<K, V>> linkedListNode = doublyLinkedList.removeTail();
|
||||||
|
if (linkedListNode.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
linkedListNodeMap.remove(linkedListNode.getElement().getKey());
|
||||||
|
return true;
|
||||||
|
} finally {
|
||||||
|
writeLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.baeldung.lrucache;
|
||||||
|
|
||||||
|
public interface LinkedListNode<V> {
|
||||||
|
boolean hasElement();
|
||||||
|
|
||||||
|
boolean isEmpty();
|
||||||
|
|
||||||
|
V getElement() throws NullPointerException;
|
||||||
|
|
||||||
|
void detach();
|
||||||
|
|
||||||
|
DoublyLinkedList<V> getListReference();
|
||||||
|
|
||||||
|
LinkedListNode<V> setPrev(LinkedListNode<V> prev);
|
||||||
|
|
||||||
|
LinkedListNode<V> setNext(LinkedListNode<V> next);
|
||||||
|
|
||||||
|
LinkedListNode<V> getPrev();
|
||||||
|
|
||||||
|
LinkedListNode<V> getNext();
|
||||||
|
|
||||||
|
LinkedListNode<V> search(V value);
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package com.baeldung.lrucache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by arash on 09.07.21.
|
||||||
|
*/
|
||||||
|
public class Node<T> implements LinkedListNode<T> {
|
||||||
|
private T value;
|
||||||
|
private DoublyLinkedList<T> list;
|
||||||
|
private LinkedListNode next;
|
||||||
|
private LinkedListNode prev;
|
||||||
|
|
||||||
|
public Node(T value, LinkedListNode<T> next, DoublyLinkedList<T> list) {
|
||||||
|
this.value = value;
|
||||||
|
this.next = next;
|
||||||
|
this.setPrev(next.getPrev());
|
||||||
|
this.prev.setNext(this);
|
||||||
|
this.next.setPrev(this);
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasElement() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getElement() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void detach() {
|
||||||
|
this.prev.setNext(this.getNext());
|
||||||
|
this.next.setPrev(this.getPrev());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DoublyLinkedList<T> getListReference() {
|
||||||
|
return this.list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> setPrev(LinkedListNode<T> prev) {
|
||||||
|
this.prev = prev;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> setNext(LinkedListNode<T> next) {
|
||||||
|
this.next = next;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> getPrev() {
|
||||||
|
return this.prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> getNext() {
|
||||||
|
return this.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinkedListNode<T> search(T value) {
|
||||||
|
return this.getElement() == value ? this : this.getNext().search(value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.baeldung.lrucache;
|
||||||
|
|
||||||
|
import com.baeldung.lrucache.Cache;
|
||||||
|
import com.baeldung.lrucache.LRUCache;
|
||||||
|
import org.hamcrest.core.AllOf;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static org.hamcrest.core.Is.is;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class LRUCacheUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addSomeDataToCache_WhenGetData_ThenIsEqualWithCacheElement() {
|
||||||
|
LRUCache<String, String> lruCache = new LRUCache<>(3);
|
||||||
|
lruCache.put("1", "test1");
|
||||||
|
lruCache.put("2", "test2");
|
||||||
|
lruCache.put("3", "test3");
|
||||||
|
assertEquals(lruCache.get("1").get(), "test1");
|
||||||
|
assertEquals(lruCache.get("2").get(), "test2");
|
||||||
|
assertEquals(lruCache.get("3").get(), "test3");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addDataToCacheToTheNumberOfSize_WhenAddOneMoreData_ThenLeastRecentlyDataWillEvict() {
|
||||||
|
LRUCache<String, String> lruCache = new LRUCache<>(3);
|
||||||
|
lruCache.put("1", "test1");
|
||||||
|
lruCache.put("2", "test2");
|
||||||
|
lruCache.put("3", "test3");
|
||||||
|
lruCache.put("4", "test4");
|
||||||
|
assertEquals(lruCache.get("1").isPresent(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void runMultiThreadTask_WhenPutDataInConcurrentToCache_ThenNoDataLost() throws Exception {
|
||||||
|
final int size = 50;
|
||||||
|
final ExecutorService executorService = Executors.newFixedThreadPool(5);
|
||||||
|
Cache<Integer, String> cache = new LRUCache<>(size);
|
||||||
|
CountDownLatch countDownLatch = new CountDownLatch(size);
|
||||||
|
try {
|
||||||
|
IntStream.range(0, size).<Runnable>mapToObj(key -> () -> {
|
||||||
|
cache.put(key, "value" + key);
|
||||||
|
System.out.println(Thread.currentThread().getName() + " " + key);
|
||||||
|
countDownLatch.countDown();
|
||||||
|
}).forEach(executorService::submit);
|
||||||
|
countDownLatch.await();
|
||||||
|
} finally {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
assertEquals(cache.size(), size);
|
||||||
|
IntStream.range(0, size).forEach(i -> assertEquals(cache.get(i).get(), "value" + i));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue