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:
Arash Ariani 2021-07-17 14:11:26 +04:30 committed by GitHub
parent 978bf7f543
commit 17814a1468
8 changed files with 534 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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