方法一:手动
整体需要两个模块,HashMap用于存储key及其对应值,链表用于存储各个节点使用时间排序(因为要频繁删除、添加,用双向链表方便操作,时间复杂度降到O(1))
首先构造双向链表节点类:
class Node {
int val;
int key;
Node pre;
Node next;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
其次依据该节点类构造双向链表类:
class DoubleLinked {
private Node head;
private Node tail;
private int size;
public DoubleLinked() {
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.pre = head;
this.size = 0;
}
}
头尾两个节点并不存储数据,因为在该环境下,从头到位表示使用由旧到新,随时需要头结点用于去除第一个(最老)节点,尾结点用于插入新节点。
思考所要用到的方法,首先队尾是最近使用的节点,那么有方法用于直接在队尾插入节点(更新最新节点的关键)。伴随着最新节点就是在队尾添加该节点,那么原节点也应该被删除,这就是第二个方法,移除指定节点。
当容量放不下,需要淘汰最老节点,这就是第三个方法,移除第一个节点。
public void addLast(Node node) {
node.next = tail;
node.pre = tail.pre;
tail.pre.next = node;
tail.pre = node;
size++;
}
public void removeNode(Node node) {
node.pre.next = node.next;
node.next.pre = node.pre;
size--;
}
public Node removeFirst() {
if (head == tail) {
return null;
}
Node first = head.next;
removeNode(first);
return first;
}
有了双向链表工具,就可以开始写真正LRU实现部分:
再实现其get和put方法前,有一些前置需要考虑清楚。
get流程:
首先在map查找是否有所需节点,没有直接返回-1,若有需要将该节点提升为最近使用,返回该节点值。
put流程:
首先map查找是否有节点,没有需要将该节点记录为最近使用,并在map中添加映射关系,再判断是否超容量,超了需要除去最老节点;有则需要删除原节点,将有新值的节点更新为最近使用。
上述流程有公共部分可以集成为对应方法,如:
删除节点:由Map获取节点后,将其在链表、map都删除
提升节点最近使用:在删除节点后,在链表尾部添加
添加新节点:在map中添加映射关系,在链表尾部添加节点
删除最老节点:删除链表头节点,map删除映射
private void makePriority(int key) {
Node node = map.get(key);
doubleLinked.removeNode(node);
doubleLinked.addLast(node);
}
private void addRecent(int key, int val) {
Node res = new Node(key, val);
doubleLinked.addLast(res);
map.put(key, res);
}
private void deleteKey(int key) {
Node node = map.get(key);
doubleLinked.removeNode(node);
map.remove(key);
}
private void removeOld() {
Node node = doubleLinked.removeFirst();
map.remove(node.key);
}
搭配方法即可实现最终get、put方法:
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
makePriority(key);
return map.get(key).val;
}
public void put(int key, int val) {
if (map.containsKey(key)) {
deleteKey(key);
addRecent(key, val);
return;
}
if (doubleLinked.size > this.cap) {
removeOld();
}
addRecent(key, val);
}
整体:
public class LRU {
class Node {
int val;
int key;
Node pre;
Node next;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
class DoubleLinked {
private Node head;
private Node tail;
private int size;
public DoubleLinked() {
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.pre = head;
this.size = 0;
}
public void addLast(Node node) {
node.next = tail;
node.pre = tail.pre;
tail.pre.next = node;
tail.pre = node;
size++;
}
public void removeNode(Node node) {
node.pre.next = node.next;
node.next.pre = node.pre;
size--;
}
public Node removeFirst() {
if (head == tail) {
return null;
}
Node first = head.next;
removeNode(first);
return first;
}
public int getSize() {
return size;
}
}
private HashMap<Integer, Node> map;
private DoubleLinked doubleLinked;
private int cap;
public LRU(int cap) {
this.map = new HashMap<>();
this.doubleLinked = new DoubleLinked();
this.cap = cap;
}
private void makePriority(int key) {
Node node = map.get(key);
doubleLinked.removeNode(node);
doubleLinked.addLast(node);
}
private void addRecent(int key, int val) {
Node res = new Node(key, val);
doubleLinked.addLast(res);
map.put(key, res);
}
private void deleteKey(int key) {
Node node = map.get(key);
doubleLinked.removeNode(node);
map.remove(key);
}
private void removeOld() {
Node node = doubleLinked.removeFirst();
map.remove(node.key);
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
makePriority(key);
return map.get(key).val;
}
public void put(int key, int val) {
if (map.containsKey(key)) {
deleteKey(key);
addRecent(key, val);
return;
}
if (doubleLinked.size > this.cap) {
removeOld();
}
addRecent(key, val);
}
}
方法二:
java本身容器LinkedHashMap就是在HashMap(无序)基础上添加了保持加入顺序的特性,完美符合需求。
注:LinkedHashMap只有在新加入键值对才会排序,更改已有键对应值并不会排序。
public class LRU {
int cap;
LinkedHashMap<Integer, Integer> cache = new LinkedHashMap<>();
public LRU(int cap) {
this.cap = cap;
}
private void makePriority(int key) {
int val = cache.get(key);
cache.remove(key);
cache.put(key, val);
}
public int get(int key) {
if (!cache.containsKey(key)) {
return -1;
}
makePriority(key);
return cache.get(key);
}
public void put(int key, int val) {
if (cache.containsKey(key)) {
cache.put(key, val);
makePriority(key);
return;
}
if (cache.size() > this.cap) {
int old = cache.keySet().iterator().next();
cache.remove(old);
}
cache.put(key, val);
}
}