题目描述
//评测题目:
// 设计和构建一个“最近最久未使用”缓存(LRU),该缓存会删除最近最久未使用的项目。
// 缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。
// 当缓存被填满时,它应该删除最近最久未使用的项目。
// 它应该支持以下操作: 获取数据 get 和 写入数据 put 。
// 获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
// 写入数据 put(key, value) - 如果密钥不存在,则写入其数据值,如果存在则覆盖数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最久未使用的数据值,从而为新的数据值留出空间
要求:可以使用基本Map,不得直接使用LinkedHashMap处理
个人思路
题解是key-value形式,所以底层可直接用HashMap做get,set。同时用双向列表来记录活跃顺序,两个不保存数据的空节点head和tail,可以更方便处理。map的value 存放链表节点。
代码
import java.util.HashMap;
import java.util.Map;
public class MyLRU {
private final int maxLength;
private int size = 0;
private final Node head = newNode(null, null);
private final Node tail = newNode(null, null);
private Map<Object, Node> map = new HashMap<>();
public static class Node {
public Object key;
public Object value;
public Node next;
public Node pre;
}
MyLRU(int maxSize) {
this.maxLength = maxSize;
head.next = tail;
tail.pre = head;
}
public void put(Object key, Object value) {
if (!map.containsKey(key)) {
Node e = newNode(key, value);
addNode(e);
map.put(key, e);
} else {
Node e = map.get(key);
e.value = value;
flush(e);
}
}
public Object get(Object key) {
if (!map.containsKey(key)) {
return -1;
}
Node e = map.get(key);
flush(e);
return e.value;
}
private Node newNode(Object key, Object value) {
Node node = new Node();
node.key = key;
node.value = value;
return node;
}
private void flush(Node e) {
e.pre.next = e.next;
e.next.pre = e.pre;
tail.pre.next = e;
e.pre = tail.pre;
e.next = tail;
tail.pre = e;
}
private void addNode(Node e) {
e.pre = tail.pre;
e.next = tail;
tail.pre.next = e;
tail.pre = e;
if (size == maxLength) {
map.remove(head.next.key);
head.next.next.pre = head;
head.next = head.next.next;
} else {
size++;
}
}
}
并发场景下问题
最后面试官又针对代码并发情况下的安全问题问了下,以及怎么解决:
1、hashmap本身的线程安全性问题,可以改为ConcurrentHashMap;
2、指针操作本身有线程安全问题,会导致活跃节点丢失和顺序不一致。写写场景下,活跃节点覆盖;写读场景下,会导致可能可能淘汰了刚读的数据;读读场景下,活跃顺序与读取顺序可能不一致,容量小时淘汰影响大,容量大时影响较小。考虑对代码块进行sychronized同步处理,用类锁。