LRU缓存实现(哈希表 + 双向链表)
使用哈希表进行定位,找出缓存项在双向链表中的位置,随后将其移动到双向链表的头部。具体的方法如下:
对于 get 操作,首先判断 key 是否存在:
如果 key 不存在,则返回 −1;
如果 key 存在,则 key 对应的节点是最近被使用的节点。通过哈希表定位到该节点在双向链表中的位置,并将其移动到双向链表的头部,最后返回该节点的值。
对于 put 操作,首先判断 key 是否存在:
如果 key 不存在,使用 key 和 value 创建一个新的节点,在双向链表的头部添加该节点,并将 key 和该节点添加进哈希表中。然后判断双向链表的节点数是否超出容量,如果超出容量,则删除双向链表的尾部节点,并删除哈希表中对应的项;
如果 key 存在,则与 get 操作类似,先通过哈希表定位,再将对应的节点的值更新为 value,并将该节点移到双向链表的头部。
package com.cm.lru;
import java.util.HashMap;
import java.util.Map;
/**
* lru实现.
* @date: 2020-07-16
* @version: 1.0
* @author: hahaha@sina.com
*/
public class LRUCache {
//定义双向链表
class NodeList{
//key
int key;
//value
int value;
//上一个节点
NodeList prev;
//下一个节点
NodeList next;
//无参构造
public NodeList(){}
//有参构造
public NodeList(int key, int value){this.key = key; this.value = value;}
}
//初始容量
private int size;
//最大容量
private int capacity;
//头部节点
private NodeList head;
//尾部节点
private NodeList tail;
//缓存容器
private Map<Integer, NodeList> cache = new HashMap<>();
//LRUCache构造器
public LRUCache(Integer capacity){
this.size = 0;
this.capacity = capacity;
// 使用伪头部和伪尾部节点
this.head = new NodeList();
this.tail = new NodeList();
//伪头节点的下一个节点指向-> 伪尾节点
head.next = tail;
//伪尾节点的上一个节点指向-> 伪头节点
tail.prev = head;
}
/**
* get操作
*/
public Integer get(Integer key){
//根据key获取value所在节点
NodeList nodeList = cache.get(key);
//判断获取节点是否为空
if(nodeList == null){
//为空返回-1
return -1;
}
//移往头部
moveToHead(nodeList);
//返回拿到的节点value
return nodeList.value;
}
/**
* put操作
*/
public void put(Integer key, Integer value){
//根据key获取节点
NodeList node = cache.get(key);
//key是否存在
if(node == null){
//当前key不存在,创建新的节点
NodeList nodes = new NodeList(key,value);
//添加进hash表--map
cache.put(key,nodes);
//添加至头部
addToHead(nodes);
//初始容量减2
++size;
//容量是否超限
if(size > capacity){
//如果超出容量 删除双向链表的尾部节点
NodeList nodeList = removeTail();
//删除hash中映射
cache.remove(nodeList.key);
//初始容量减1
--size;
}
}else{
//key存在,更新值
node.value = value;
//当前节点移往头部
moveToHead(node);
}
}
/**
* 当前节点移往头部
*/
public void moveToHead(NodeList node){
//删除当前节点
removeNode(node);
//新增当前节点
addToHead(node);
}
/**
* 删除当前节点
*/
public void removeNode(NodeList node){
//改变当前节点的上一个节点的下一个指向->当前节点的下一个节点
node.prev.next = node.next;
//改变当前节点的下一个节点的上一个指向->当前节点的上一个节点
node.next.prev = node.prev;
}
/**
* 添加节点
*/
public void addToHead(NodeList node){
//改变当前节点(添加的节点)的上一个节点指向->伪头节点
node.prev = head;
//改变当前节点(添加的节点)的下一个节点指向->伪头节点的下一个节点
node.next = head.next;
//改变伪头结点的下一个节点中的上一个节点指向->当前节点(添加的节点)
head.next.prev = node;
//改变伪头结点的下一个指向->当前节点(添加的节点)
head.next = node;
}
/**
* 删除尾部节点
*/
public NodeList removeTail(){
//拿到伪尾节点的上一个节点
NodeList prev = this.tail.prev;
//删除伪尾节点的上一个节点
removeNode(prev);
//返回伪尾节点的上一个节点
return prev;
}
}
参考来源:
代码来源: LRU缓存机制.