Skip to content

Commit c389fd6

Browse files
authored
BAEL-4464 : how to implement LRU-Cache in java codes added (eugenp#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
1 parent ac073dc commit c389fd6

File tree

8 files changed

+534
-0
lines changed

8 files changed

+534
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.baeldung.lrucache;
2+
3+
import java.util.Optional;
4+
5+
public interface Cache<K, V> {
6+
boolean put(K key, V value);
7+
8+
Optional<V> get(K key);
9+
10+
int size();
11+
12+
boolean isEmpty();
13+
14+
void clear();
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.baeldung.lrucache;
2+
3+
/**
4+
* Created by arash on 09.07.21.
5+
*/
6+
7+
public class CacheElement<K,V> {
8+
private K key;
9+
private V value;
10+
11+
public CacheElement(K key, V value) {
12+
this.value = value;
13+
this.key = key;
14+
}
15+
16+
public K getKey() {
17+
return key;
18+
}
19+
20+
public void setKey(K key) {
21+
this.key = key;
22+
}
23+
24+
public V getValue() {
25+
return value;
26+
}
27+
28+
public void setValue(V value) {
29+
this.value = value;
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package com.baeldung.lrucache;
2+
3+
import java.util.Collection;
4+
import java.util.concurrent.atomic.AtomicInteger;
5+
import java.util.concurrent.locks.ReentrantReadWriteLock;
6+
7+
public class DoublyLinkedList<T> {
8+
9+
private DummyNode<T> dummyNode;
10+
private LinkedListNode<T> head;
11+
private LinkedListNode<T> tail;
12+
private AtomicInteger size;
13+
private ReentrantReadWriteLock.ReadLock readLock;
14+
private ReentrantReadWriteLock.WriteLock writeLock;
15+
16+
17+
public DoublyLinkedList() {
18+
this.dummyNode = new DummyNode<T>(this);
19+
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
20+
readLock = lock.readLock();
21+
writeLock = lock.writeLock();
22+
clear();
23+
}
24+
25+
public void clear() {
26+
writeLock.lock();
27+
try {
28+
head = dummyNode;
29+
tail = dummyNode;
30+
size = new AtomicInteger(0);
31+
} finally {
32+
writeLock.unlock();
33+
}
34+
}
35+
36+
public int size() {
37+
readLock.lock();
38+
try {
39+
return size.get();
40+
} finally {
41+
readLock.unlock();
42+
}
43+
}
44+
45+
public boolean isEmpty() {
46+
readLock.lock();
47+
try {
48+
return head.isEmpty();
49+
} finally {
50+
readLock.unlock();
51+
}
52+
}
53+
54+
public boolean contains(T value) {
55+
readLock.lock();
56+
try {
57+
return search(value).hasElement();
58+
} finally {
59+
readLock.unlock();
60+
}
61+
}
62+
63+
public LinkedListNode<T> search(T value) {
64+
readLock.lock();
65+
try {
66+
return head.search(value);
67+
} finally {
68+
readLock.unlock();
69+
}
70+
}
71+
72+
public LinkedListNode<T> add(T value) {
73+
writeLock.lock();
74+
try {
75+
head = new Node<T>(value, head, this);
76+
if (tail.isEmpty()) {
77+
tail = head;
78+
}
79+
size.incrementAndGet();
80+
return head;
81+
} finally {
82+
writeLock.unlock();
83+
}
84+
}
85+
86+
public boolean addAll(Collection<T> values) {
87+
writeLock.lock();
88+
try {
89+
for (T value : values) {
90+
if (add(value).isEmpty()) {
91+
return false;
92+
}
93+
}
94+
return true;
95+
} finally {
96+
writeLock.unlock();
97+
}
98+
}
99+
100+
public LinkedListNode<T> remove(T value) {
101+
writeLock.lock();
102+
try {
103+
LinkedListNode<T> linkedListNode = head.search(value);
104+
if (!linkedListNode.isEmpty()) {
105+
if (linkedListNode == tail) {
106+
tail = tail.getPrev();
107+
}
108+
if (linkedListNode == head) {
109+
head = head.getNext();
110+
}
111+
linkedListNode.detach();
112+
size.decrementAndGet();
113+
}
114+
return linkedListNode;
115+
} finally {
116+
writeLock.unlock();
117+
}
118+
}
119+
120+
public LinkedListNode<T> removeTail() {
121+
writeLock.lock();
122+
try {
123+
LinkedListNode<T> oldTail = tail;
124+
if (oldTail == head) {
125+
tail = head = dummyNode;
126+
} else {
127+
tail = tail.getPrev();
128+
oldTail.detach();
129+
}
130+
if (!oldTail.isEmpty()) {
131+
size.decrementAndGet();
132+
}
133+
return oldTail;
134+
} finally {
135+
writeLock.unlock();
136+
}
137+
}
138+
139+
public LinkedListNode<T> moveToFront(LinkedListNode<T> node) {
140+
return node.isEmpty() ? dummyNode : updateAndMoveToFront(node, node.getElement());
141+
}
142+
143+
public LinkedListNode<T> updateAndMoveToFront(LinkedListNode<T> node, T newValue) {
144+
writeLock.lock();
145+
try {
146+
if (node.isEmpty() || (this != (node.getListReference()))) {
147+
return dummyNode;
148+
}
149+
detach(node);
150+
add(newValue);
151+
return head;
152+
} finally {
153+
writeLock.unlock();
154+
}
155+
}
156+
157+
private void detach(LinkedListNode<T> node) {
158+
if (node != tail) {
159+
node.detach();
160+
if (node == head) {
161+
head = head.getNext();
162+
}
163+
size.decrementAndGet();
164+
} else {
165+
removeTail();
166+
}
167+
}
168+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.baeldung.lrucache;
2+
3+
/**
4+
* Created by arash on 09.07.21.
5+
*/
6+
public class DummyNode<T> implements LinkedListNode<T> {
7+
private DoublyLinkedList<T> list;
8+
9+
public DummyNode(DoublyLinkedList<T> list) {
10+
this.list = list;
11+
}
12+
13+
@Override
14+
public boolean hasElement() {
15+
return false;
16+
}
17+
18+
@Override
19+
public boolean isEmpty() {
20+
return true;
21+
}
22+
23+
@Override
24+
public T getElement() throws NullPointerException {
25+
throw new NullPointerException();
26+
}
27+
28+
@Override
29+
public void detach() {
30+
return;
31+
}
32+
33+
@Override
34+
public DoublyLinkedList<T> getListReference() {
35+
return list;
36+
}
37+
38+
@Override
39+
public LinkedListNode<T> setPrev(LinkedListNode<T> next) {
40+
return next;
41+
}
42+
43+
@Override
44+
public LinkedListNode<T> setNext(LinkedListNode<T> prev) {
45+
return prev;
46+
}
47+
48+
@Override
49+
public LinkedListNode<T> getPrev() {
50+
return this;
51+
}
52+
53+
@Override
54+
public LinkedListNode<T> getNext() {
55+
return this;
56+
}
57+
58+
@Override
59+
public LinkedListNode<T> search(T value) {
60+
return this;
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.baeldung.lrucache;
2+
3+
import java.util.Hashtable;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import java.util.concurrent.ConcurrentHashMap;
7+
import java.util.concurrent.locks.ReentrantReadWriteLock;
8+
9+
public class LRUCache<K, V> implements Cache<K, V> {
10+
private int size;
11+
private Map<K, LinkedListNode<CacheElement<K, V>>> linkedListNodeMap;
12+
private DoublyLinkedList<CacheElement<K, V>> doublyLinkedList;
13+
private ReentrantReadWriteLock.ReadLock readLock;
14+
private ReentrantReadWriteLock.WriteLock writeLock;
15+
16+
public LRUCache(int size) {
17+
this.size = size;
18+
this.linkedListNodeMap = new Hashtable<>(size);
19+
this.doublyLinkedList = new DoublyLinkedList<>();
20+
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
21+
this.readLock = lock.readLock();
22+
this.writeLock = lock.writeLock();
23+
}
24+
25+
@Override
26+
public boolean put(K key, V value) {
27+
writeLock.lock();
28+
try {
29+
CacheElement<K, V> item = new CacheElement<K, V>(key, value);
30+
LinkedListNode<CacheElement<K, V>> newNode;
31+
if (this.linkedListNodeMap.containsKey(key)) {
32+
LinkedListNode<CacheElement<K, V>> node = this.linkedListNodeMap.get(key);
33+
newNode = doublyLinkedList.updateAndMoveToFront(node, item);
34+
} else {
35+
if (this.size() >= this.size) {
36+
this.evictElement();
37+
}
38+
newNode = this.doublyLinkedList.add(item);
39+
}
40+
if (newNode.isEmpty()) {
41+
return false;
42+
}
43+
this.linkedListNodeMap.put(key, newNode);
44+
return true;
45+
} finally {
46+
writeLock.unlock();
47+
}
48+
}
49+
50+
@Override
51+
public Optional<V> get(K key) {
52+
readLock.lock();
53+
try {
54+
LinkedListNode<CacheElement<K, V>> linkedListNode = this.linkedListNodeMap.get(key);
55+
if (linkedListNode != null && !linkedListNode.isEmpty()) {
56+
linkedListNodeMap.put(key, this.doublyLinkedList.moveToFront(linkedListNode));
57+
return Optional.of(linkedListNode.getElement().getValue());
58+
}
59+
return Optional.empty();
60+
} finally {
61+
readLock.unlock();
62+
}
63+
}
64+
65+
@Override
66+
public int size() {
67+
readLock.lock();
68+
try {
69+
return doublyLinkedList.size();
70+
} finally {
71+
readLock.unlock();
72+
}
73+
}
74+
75+
@Override
76+
public boolean isEmpty() {
77+
return size() == 0;
78+
}
79+
80+
@Override
81+
public void clear() {
82+
writeLock.lock();
83+
try {
84+
linkedListNodeMap.clear();
85+
doublyLinkedList.clear();
86+
} finally {
87+
writeLock.unlock();
88+
}
89+
}
90+
91+
92+
private boolean evictElement() {
93+
writeLock.lock();
94+
try {
95+
LinkedListNode<CacheElement<K, V>> linkedListNode = doublyLinkedList.removeTail();
96+
if (linkedListNode.isEmpty()) {
97+
return false;
98+
}
99+
linkedListNodeMap.remove(linkedListNode.getElement().getKey());
100+
return true;
101+
} finally {
102+
writeLock.unlock();
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)