-
Notifications
You must be signed in to change notification settings - Fork 19.9k
/
Copy pathLRUCache.java
235 lines (210 loc) · 6.51 KB
/
LRUCache.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package com.thealgorithms.datastructures.caches;
import java.util.HashMap;
import java.util.Map;
/**
* A Least Recently Used (LRU) Cache implementation.
*
* <p>An LRU cache is a fixed-size cache that maintains items in order of use. When the cache reaches
* its capacity and a new item needs to be added, it removes the least recently used item first.
* This implementation provides O(1) time complexity for both get and put operations.</p>
*
* <p>Features:</p>
* <ul>
* <li>Fixed-size cache with configurable capacity</li>
* <li>Constant time O(1) operations for get and put</li>
* <li>Thread-unsafe - should be externally synchronized if used in concurrent environments</li>
* <li>Supports null values but not null keys</li>
* </ul>
*
* <p>Implementation Details:</p>
* <ul>
* <li>Uses a HashMap for O(1) key-value lookups</li>
* <li>Maintains a doubly-linked list for tracking access order</li>
* <li>The head of the list contains the least recently used item</li>
* <li>The tail of the list contains the most recently used item</li>
* </ul>
*
* <p>Example usage:</p>
* <pre>
* LRUCache<String, Integer> cache = new LRUCache<>(3); // Create cache with capacity 3
* cache.put("A", 1); // Cache: A=1
* cache.put("B", 2); // Cache: A=1, B=2
* cache.put("C", 3); // Cache: A=1, B=2, C=3
* cache.get("A"); // Cache: B=2, C=3, A=1 (A moved to end)
* cache.put("D", 4); // Cache: C=3, A=1, D=4 (B evicted)
* </pre>
*
* @param <K> the type of keys maintained by this cache
* @param <V> the type of mapped values
*/
public class LRUCache<K, V> {
private final Map<K, Entry<K, V>> data = new HashMap<>();
private Entry<K, V> head;
private Entry<K, V> tail;
private int cap;
private static final int DEFAULT_CAP = 100;
public LRUCache() {
setCapacity(DEFAULT_CAP);
}
public LRUCache(int cap) {
setCapacity(cap);
}
/**
* Returns the current capacity of the cache.
*
* @param newCapacity the new capacity of the cache
*/
private void setCapacity(int newCapacity) {
checkCapacity(newCapacity);
for (int i = data.size(); i > newCapacity; i--) {
Entry<K, V> evicted = evict();
data.remove(evicted.getKey());
}
this.cap = newCapacity;
}
/**
* Evicts the least recently used item from the cache.
*
* @return the evicted entry
*/
private Entry<K, V> evict() {
if (head == null) {
throw new RuntimeException("cache cannot be empty!");
}
Entry<K, V> evicted = head;
head = evicted.getNextEntry();
head.setPreEntry(null);
evicted.setNextEntry(null);
return evicted;
}
/**
* Checks if the capacity is valid.
*
* @param capacity the capacity to check
*/
private void checkCapacity(int capacity) {
if (capacity <= 0) {
throw new RuntimeException("capacity must greater than 0!");
}
}
/**
* Returns the value to which the specified key is mapped, or null if this cache contains no
* mapping for the key.
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or null if this cache contains no
* mapping for the key
*/
public V get(K key) {
if (!data.containsKey(key)) {
return null;
}
final Entry<K, V> entry = data.get(key);
moveNodeToLast(entry);
return entry.getValue();
}
/**
* Moves the specified entry to the end of the list.
*
* @param entry the entry to move
*/
private void moveNodeToLast(Entry<K, V> entry) {
if (tail == entry) {
return;
}
final Entry<K, V> preEntry = entry.getPreEntry();
final Entry<K, V> nextEntry = entry.getNextEntry();
if (preEntry != null) {
preEntry.setNextEntry(nextEntry);
}
if (nextEntry != null) {
nextEntry.setPreEntry(preEntry);
}
if (head == entry) {
head = nextEntry;
}
tail.setNextEntry(entry);
entry.setPreEntry(tail);
entry.setNextEntry(null);
tail = entry;
}
/**
* Associates the specified value with the specified key in this cache.
*
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
*/
public void put(K key, V value) {
if (data.containsKey(key)) {
final Entry<K, V> existingEntry = data.get(key);
existingEntry.setValue(value);
moveNodeToLast(existingEntry);
return;
}
Entry<K, V> newEntry;
if (data.size() == cap) {
newEntry = evict();
data.remove(newEntry.getKey());
} else {
newEntry = new Entry<>();
}
newEntry.setKey(key);
newEntry.setValue(value);
addNewEntry(newEntry);
data.put(key, newEntry);
}
/**
* Adds a new entry to the end of the list.
*
* @param newEntry the entry to add
*/
private void addNewEntry(Entry<K, V> newEntry) {
if (data.isEmpty()) {
head = newEntry;
tail = newEntry;
return;
}
tail.setNextEntry(newEntry);
newEntry.setPreEntry(tail);
newEntry.setNextEntry(null);
tail = newEntry;
}
static final class Entry<I, J> {
private Entry<I, J> preEntry;
private Entry<I, J> nextEntry;
private I key;
private J value;
Entry() {
}
Entry(Entry<I, J> preEntry, Entry<I, J> nextEntry, I key, J value) {
this.preEntry = preEntry;
this.nextEntry = nextEntry;
this.key = key;
this.value = value;
}
public Entry<I, J> getPreEntry() {
return preEntry;
}
public void setPreEntry(Entry<I, J> preEntry) {
this.preEntry = preEntry;
}
public Entry<I, J> getNextEntry() {
return nextEntry;
}
public void setNextEntry(Entry<I, J> nextEntry) {
this.nextEntry = nextEntry;
}
public I getKey() {
return key;
}
public void setKey(I key) {
this.key = key;
}
public J getValue() {
return value;
}
public void setValue(J value) {
this.value = value;
}
}
}