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