LRU缓存实现思路:用LinkedHashMap做存储结构,因为它内部维护了一个双向链表来记录插入顺序。
public class LRUCache {
private int capacity;
private LRUMap cache;
public LRUCache(int capacity) {
this.capacity = capacity;
this.cache = new LRUMap(capacity);
}
//通过创建一个匿名内部类的方式覆盖了removeEldestEntry方法
private class LRUMap extends LinkedHashMap<Integer, Integer> {
public LRUMap(int initialCapacity) {
super(initialCapacity, 0.75f, true);
}
//removeEldestEntry,默认实现返回false
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > LRUCache.this.capacity;
}
}
public int get(int key) {
return cache.getOrDefault(key, -1);
}
public void put(int key, int value) {
cache.put(key, value);
}
}
LRU的缓存实现很简单,LFU比较复杂。LFU策略中,当缓存满时,先淘汰访问频率最低的数据。如果有多个数据项的访问频率相同,就先淘汰最早被访问的数据。
public class LFUCache {
HashMap<Integer, Integer> entryMap; //存储键
HashMap<Integer, Integer> countMap; //存储每个key及其访问次数
HashMap<Integer, LinkedHashSet<Integer>> lists; //存储每个使用频率对应的键的集合。
int capacity;
int min = -1;
public LFUCache(int capacity) {
this.capacity = capacity;
entryMap = new HashMap<>();
countMap = new HashMap<>();
lists = new HashMap<>();
lists.put(1, new LinkedHashSet<>());
}
//访问键,如果键存在,将新访问频率记录在countMap里,并从lists中移除老访问频率
public int get(int key) {
if (!entryMap.containsKey(key)) return -1;
int count = countMap.get(key);
countMap.put(key, count + 1);
lists.get(count).remove(key);
if (count == min && lists.get(count).size() == 0) {
min++;
}
if (!lists.containsKey(count + 1)) {
lists.put(count + 1, new LinkedHashSet<>());
}
lists.get(count + 1).add(key);
return entryMap.get(key);
}
//更新键,存在就直接更新值及键的使用频率,如果键不存在,添加新键到缓存中。
public void put(int key, int value) {
if (capacity <= 0) return;
if (entryMap.containsKey(key)) { //如果键存在,更新键的值,并调用get()更新键使用频率
entryMap.put(key, value);
get(key);
return;
}
//添加新键前,先检查缓存是否容量超限,超限要删除使用频率最低且最早添加的键。
if (entryMap.size() >= capacity) {
int evict = lists.get(min).iterator().next();
lists.get(min).remove(evict);
entryMap.remove(evict);
}
//将新键添加到entryMap和countMap中,并将其添加到使用频率为1的list中。
entryMap.put(key, value);
countMap.put(key, 1);
min = 1;
lists.get(1).add(key);
}
}
上述代码中的存储结构用HashMap线程不安全,改成ConcurrentHashMap确保更新entryMap、countMap和lists操作的原子性。 例:线程A在put操作,线程B在get操作。如果A在更新countMap和lists时被B中断,B看到的entryMap被更新,但counts和lists还未被更新。