携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情
我之前写的MySQL第一步介绍InnoDB引擎的时候,提到Buffer Pool里缓存页信息时使用到LRU算法,它的LRU是基于传统LRU又额外优化出new区和old区,今天想使用java实现下传统的LRU算法。
基于HashMap和双向链表实现LRU
由双向链表实现LRU中数据的存储和对使用时间新旧的维护。
1.在链表头的是最新使用的。 2.在尾部的是最旧的。也是下次要清除的。 3.如果加入的值是链表内存在的则要移动到头部。
HashMap用来快速定位数据在双向链表中位置的,减少查询时候的时间复杂度。它是可以快速的(O(1)的时间)定位,链表中某个值是否存在。(每次遍历双向链表,时间复杂度为O(n)),定位到某个值存在后能马上获得他的node节点,因为是双向链表,直接用此节点的父节点,指向此节点的子节点。在将此节点放到头部就可以了。免除了遍历查找。
bean
class DLinkedNode {
String key;
int value;
DLinkedNode pre;
DLinkedNode post;
}
LRU Cache
public class LRUCache {
private Hashtable<Integer, DLinkedNode> cache = new Hashtable<Integer, DLinkedNode>();
private int count;
private int capacity;
private DLinkedNode head, tail;
public LRUCache(int capacity) {
this.count = 0;
this.capacity = capacity;
head = new DLinkedNode();
head.pre = null;
tail = new DLinkedNode();
tail.post = null;
head.post = tail;
tail.pre = head;
}
public int get(String key) {
DLinkedNode node = cache.get(key);
if(node == null){
return -1;
}
this.moveToHead(node);
return node.value;
}
private void addNode(DLinkedNode node){
node.pre = head;
node.post = head.post;
head.post.pre = node;
head.post = node;
}
private void removeNode(DLinkedNode node){
DLinkedNode pre = node.pre;
DLinkedNode post = node.post;
pre.post = post;
post.pre = pre;
}
private void moveToHead(DLinkedNode node){
this.removeNode(node);
this.addNode(node);
}
private DLinkedNode popTail(){
DLinkedNode res = tail.pre;
this.removeNode(res);
return res;
}
public void set(String key, int value) {
DLinkedNode node = cache.get(key);
if(node == null){
DLinkedNode newNode = new DLinkedNode();
newNode.key = key;
newNode.value = value;
this.cache.put(key, newNode);
this.addNode(newNode);
++count;
if(count > capacity){
DLinkedNode tail = this.popTail();
this.cache.remove(tail.key);
--count;
}
}else{
node.value = value;
this.moveToHead(node);
}
}
}
为什么不能使用队列来存储数据?
队列只能做到先进先出,但是重复用到中间的数据时无法把中间的数据移动到顶端。
为什么不能使用单链表?
单链表能实现新来的放头部,最久不用的在尾部删除。但删除的时候需要遍历到尾部,因为单链表只有头指针。在用到已经用到过的数据时,还要遍历整合链表,来确定是否用过,然后再遍历到响应位置来剔除的节点,并重新放在头部。这效率可想而知。
这时hashmap的作用就出来了,他可以在单位1的时间判断value的值是否存在,key 直接存储节点对象,能直接定位删除对应的节点 (将比节点的父节点指向此节点的子节点)。
要通过一个节点直接获得父节点的话,通过单链表是不行的。 这时双向链表的作用也提现出来了。能直接定位到父节点。这效率就很高了。而且由于双向链表有尾指针,所以剔除最后的尾节点也十分方便,快捷。
最后,鸣谢“空山鸟语as” 大佬的文章