思路图片先理解
- 说明
-
head的next节点就是链表的最后一个(我们把链表最右边当成第一个),它就是最近最久未使用的数据
-
tail的prev节点就是链表的第一个,它代表最新最火热的数据
-
每次get一个元素,就把它放到链表第一个去,这张图如果get1,那么相当于1放到3前面去,链表从左到右就是2,3,1,了,1成为了最新最火热的数据,如下图
-
每次put一个元素的时候,需要检查容量是否还够,如果不够就需要删除head的next节点,因为它是最近最久未使用的数据
-
注意
下面代码结合注释看比较容易看懂,还有就是,其实所谓的移动,只是改变指针域,我这样画只是为了方便看,不然线满天飞,很乱
class LRUCache {
//缓存数据结构,相当于
private class CacheNode {
//设置为双向链表,为了方便插入和删除快速定位到前驱
CacheNode prev;
CacheNode next;
int key;
int value;
public CacheNode(int key, int value) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
}
//容量
private int capacity;
//实际上是HashMap来存储CacheNode的,这样能迅速找到想要查找的
private Map<Integer, CacheNode> valNodeMap = new HashMap();
//这两个指针是为了快速更新节点到最前面(tail.prev)(最新使用的),和快速删除最后一个节点(head.next)(最近最少使用的)
private CacheNode head = new CacheNode(-1, -1);
private CacheNode tail = new CacheNode(-1, -1);
public LRUCache(int capacity) {
this.capacity = capacity;
//一来先让它们两个互指
tail.prev = head;
head.next = tail;
}
public int get(int key) {
//没有这个数据直接返回-1
if (!valNodeMap.containsKey(key)) {
return -1;
}
CacheNode current = valNodeMap.get(key);
//get一个后,那个数据是最火热的,应该到最前面去即成为tail的前驱结点
//因为是双向链表,所以移动之前需要先处理它前驱结点和后继节点的指针关系
// 它前面节点的next域应该指向当前节点的next节点
current.prev.next = current.next;
// 它后面节点的prev域应该指向当前节点的前一个节点(即prev)
current.next.prev = current.prev;
//调用方法,真正移动current节点(即它成为最新最火热的节点)
moveToTail(current);
return current.value;
}
public void put(int key, int value) {
if (get(key) != -1) {
//条件满足,说明本来就有这个key,只需get方法,把它放到最前面去,并且修改它的value为新的值
valNodeMap.get(key).value = value;
return;
}
//如果容量满了,则需要删除最近最少使用的缓存节点,即head节点的next节点,因为它在最后面
if (valNodeMap.size() == capacity) {
//请注意这里,HashMap删除元素,只是从HashMap中移除,并不是真正把该对象从内存删除
//所以这里head开头的链表并未断开,仍然可以拿到next节点
valNodeMap.remove(head.next.key);
head.next = head.next.next;
head.next.prev = head;
//这种写法可能好一点点
// CacheNode record=head.next;
// head.next=head.next.next;
// head.next.prev=head;
// valNodeMap.remove(record.key);
}
CacheNode insert = new CacheNode(key, value);
//往HashMap里面插入
valNodeMap.put(key, insert);
//移到最前面去
moveToTail(insert);
}
private void moveToTail(CacheNode current) {
//画个图比较好理解,就是插入到tail的前驱节点的后面
current.prev = tail.prev;
tail.prev = current;
current.prev.next = current;
current.next = tail;
}
}