题目二:
请设计实现一个最近最少使用( Least Recently Used,LRU)缓存,要求如下两个操作的时间复杂度都是O(1):
get(key):如果缓存中存在键key,则返回它对应的值;否则返回-1
put( key, value):如果缓存中之前包含键key,则它的值设为 value;否则添加键key及对应的值 value。
在添加一个键时,如果缓存容量已经满了,则在添加新键之前删除最近最少使用的键(缓存中最长时间没有被使用过的元素)。
思路:
先来翻译翻译题目的意思:假设这里有个容量为3的缓存,依次存入key为A、B、C的元素:
得到缓存:
C --> B --> A
新增key为D的缓存
D --> C --> B
新增key为C的缓存
C --> D --> B
题目要求时间复杂度为O(1)的数据结构,那必须又要用到HashMap了,但有个问题就是HashMap无法记录键值的顺序,也就无法判断key的先后顺序了。
那有没有可以排序的map呢?有的:LinkedHashMap
LinkedHashMap
与 HashMap
的区别与联系:
LinkedHashMap
是继承于HashMap
,是基于HashMap
和双向链表来实现的。HashMap
无序;LinkedHashMap
有序,可分为插入顺序和访问顺序两种。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)。LinkedHashMap
存取数据,还是跟HashMap
一样使用的Entry[]的方式,双向链表只是为了保证顺序。LinkedHashMap
是线程不安全的。
有了可以实现顺序的Map映射,那对于缓存的操作便迎刃而解了:
- 存数据:
- 当映射中存在key时,需先根据key移除,再放入K-V (此时存入的顺序为末位)
- 当映射中不存在时
- 如果小于容量:直接在映射后存入即可
- 大于容量:则把映射首位数据移除再做存入操作
- 取数据:
- 直接根据key获取value即可
实现:
为了方便遍历实现了
Iterable
接口流式输出
public class LRUCache implements Iterable<String> {
//缓存器容量
int cap;
public LRUCache(int cap) {
this.cap = cap;
}
LinkedHashMap<String, Integer> cache = new LinkedHashMap<>();
public int get(String key) {
return cache.getOrDefault(key, -1);
}
public void put(String key, Integer value) {
if (cache.containsKey(key)) {
//如果存在当前key则需移除后才能添加
cache.remove(key);
} else if (cache.size() >= cap) {
Iterator<String> it = cache.keySet().iterator();
//容器满了,则需要将首个移除才能装下后续缓存
String first = it.next();
cache.remove(first);
}
cache.put(key, value);
}
@Override
public Iterator<String> iterator() {
// 等价
return cache.keySet().iterator();
// var it = cache.entrySet().iterator();
// return new Iterator<>() {
// @Override
// public boolean hasNext() {
// return it.hasNext();
// }
//
// @Override
// public K next() {
// return it.next().getKey();
// }
// };
}
}
验证:
public static void main(java.lang.String[] argv) {
LRUCache lru = new LRUCache(4);
lru.put("A", 1);
lru.put("B", 2);
lru.put("C", 3);
lru.put("D", 4);
System.out.println(
"leave <-" + StreamSupport.stream(lru.spliterator(), false).map(Object::toString)
.collect(Collectors.joining("<-")));
System.out.println("C--" + lru.get("C"));
lru.put("E", 5);
lru.put("F", 4);
lru.put("C", 10);
System.out.println(
"leave <-" + StreamSupport.stream(lru.spliterator(), false).map(Object::toString)
.collect(Collectors.joining("<-")));
}
输出:
leave <-A<-B<-C<-D
C--3
leave <-D<-E<-F<-C
验证成功!