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
|
package cgeo.geocaching.utils;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Base class for caching objects. Don't mix up with a geocache !
*
* The LeastRecentlyUsedMap is basically a LinkedHashMap which can be configured to have certain modes of operation:
* <ul>
* <li> LRU_CACHE means that the elements are updated in the LinkedList on every get() access,
* so the objects that are dropped are the ones that haven't been used the longest</li>
* <li> BOUNDED means that objects are updated only when they are put,
* so the objects that are dropped are the ones that haven't been written the longest</li>
* </ul>
*/
public abstract class LeastRecentlyUsedMap<K, V> extends LinkedHashMap<K, V> {
private enum OperationModes {
LRU_CACHE, BOUNDED
}
private static final long serialVersionUID = -5077882607489806620L;
private final int maxEntries;
private final OperationModes opMode;
private RemoveHandler<V> removeHandler;
// store the HashMap parameters for serialization, as we can't access the originals in the LinkedHashMap
final int initialCapacity;
final float loadFactor;
protected LeastRecentlyUsedMap(int maxEntries, int initialCapacity, float loadFactor, OperationModes opMode) {
super(initialCapacity, loadFactor, (opMode==OperationModes.LRU_CACHE));
this.initialCapacity = initialCapacity;
this.loadFactor = loadFactor;
this.maxEntries = maxEntries;
this.opMode = opMode;
}
protected LeastRecentlyUsedMap(int maxEntries, OperationModes opMode) {
this(maxEntries, 16, 0.75f, opMode);
}
@Override
public V put(K key, V value) {
// in case the underlying Map is not running with accessOrder==true, the map won't notice any changes
// of existing keys, so for the normal BOUNDED mode we remove and put the value to get its order updated.
if (opMode == OperationModes.BOUNDED && containsKey(key)) {
// avoid trigger the remove notification
final V oldVal = super.remove(key);
put(key, value);
return oldVal;
}
return super.put(key, value);
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxEntries;
}
public int getMaxEntries() {
return maxEntries;
}
@Override
public V remove(Object key) {
V removed = super.remove(key);
if (removed != null && removeHandler != null) {
removeHandler.onRemove(removed);
}
return removed;
}
/**
* Sets a handler for remove notifications. Currently only one handler
* instance is supported
*
* @param removeHandler
* The new handler to receive notifications or null to remove a handler
*/
public void setRemoveHandler(RemoveHandler<V> removeHandler) {
this.removeHandler = removeHandler;
}
public static class LruCache<K, V> extends LeastRecentlyUsedMap<K, V> {
private static final long serialVersionUID = 9028478916221334454L;
public LruCache(int maxEntries, int initialCapacity, float loadFactor) {
super(maxEntries, initialCapacity, loadFactor, OperationModes.LRU_CACHE);
}
public LruCache(int maxEntries) {
super(maxEntries, OperationModes.LRU_CACHE);
}
}
public static class Bounded<K, V> extends LeastRecentlyUsedMap<K, V> {
private static final long serialVersionUID = -1476389304214398315L;
public Bounded(int maxEntries, int initialCapacity, float loadFactor) {
super(maxEntries, initialCapacity, loadFactor, OperationModes.BOUNDED);
}
public Bounded(int maxEntries) {
super(maxEntries, OperationModes.BOUNDED);
}
}
/**
* Interface for handlers that wish to get notified when items are
* removed from the LRUMap
*
* @param <V>
*/
public interface RemoveHandler<V> {
/**
* Method will be called on remove
*
* @param removed
* Item that has been removed
*/
void onRemove(V removed);
}
}
|