LRU缓存淘汰算法,也就是最近最少使用缓存淘汰策略实现的算法。
借助于链表,我们可以这样做:
我们可以维护一个按照访问时间从大到小有序排列的链表。当然在这算法中这个链表有长度限制,以便于当链表容量达到时执行淘汰策略。
无论是①在缓存中保存数据 ②从缓存中删除一个数据 ③在缓存中查到一个数据,这三种都要先进行缓存中数据的查找操作,即
添加时:我们先要在缓存中查找这个数据是否存在。如果没有找到则直接将数据放到链表头部,如果找到的话,将这个数据移动到链表头部。
删除时:我们先要在缓存中查找这个数据是否存在。如果找到则删除这个数据。
查找时:我们先要在缓存中查找这个数据是否存在。如果找到则返回这个数据。
所以,如果单纯使用链表去实现LRU缓存淘汰算法,时间复杂度是O(n).
为了提高时间复杂度,我们可以借助于散列表,散列表查找数据时间复杂度O(1),这里使用散列表加双向链表来实现LRU缓存淘汰算法。
添加时:我们首先根据key在散列表中找到要删除的节点,时间复杂度O(1)。找到后将这个数据移动到链表头部。找不到则添加到链表头部,如果链表满的话还要删除链表的尾结点。
删除时:我们在时间复杂度O(1)内找到这个数据然后执行删除。
查找时:直接根据key在时间复杂度为O(1)内就可以找到了。
代码:
package com.study.algorithm.hashtable;
import java.util.HashMap;
/**
* @Auther: JeffSheng
* @Date: 2019/8/30 11:23
* @Description:
* 基于散列表+双向链表 实现LRU算法
*
*/
public class LruBaseHashTable<K,V> {
/**
* 默认链表容量
*/
private final static Integer DEFAULT_CAPACITY = 10;
/**
* 头结点
*/
private DoubleNode<K,V> headNode;
/**
* 尾结点
*/
private DoubleNode<K,V> tailNode;
/**
* 链表长度
*/
private Integer length;
/**
* 链表容量
*/
private Integer capacity;
/**
* 散列表存储key以及对应链表
*/
private HashMap<K,DoubleNode<K,V>> table;
static class DoubleNode<K,V>{
private K key;
private V value;
private DoubleNode<K,V> prev;
private DoubleNode<K,V> next;
DoubleNode(){}
DoubleNode(K key,V value){
this.key=key;
this.value=value;
}
}
/**
* 初始化散列表
* @param capacity
*/
public LruBaseHashTable(int capacity) {
this.length = 0;
this.capacity = capacity;
headNode = new DoubleNode<>();
tailNode = new DoubleNode<>();
headNode.next = tailNode;
tailNode.prev = headNode;
table = new HashMap<>();
}
public LruBaseHashTable() {
this(DEFAULT_CAPACITY);
}
/**
* 新增节点
* @param key
* @param value
*/
public void add(K key,V value){
DoubleNode<K,V> node = table.get(key);
if(node==null){
DoubleNode<K,V> newNode=new DoubleNode<>(key,value);
table.put(key,newNode);
//将新结点插入到链表头部
addNodeToHead(newNode);
//如果链表容量已超
if(++length >capacity){
//从散列表删除尾结点元素
DoubleNode<K,V> tail = popTail();
table.remove(tail.key);
length--;
}
}else{
//添加的节点存在,则覆盖此节点值
node.value = value;
//然后将存在的节点移动到链表头部
moveToHead(node);
}
}
/**
* 将节点移动到头部
*
* @param node
*/
private void moveToHead(DoubleNode<K, V> node) {
removeNode(node);
addNodeToHead(node);
}
/**
* 弹出尾部数据节点
*/
private DoubleNode<K, V> popTail() {
DoubleNode<K, V> node = tailNode.prev;
removeNode(node);
return node;
}
/**
* 移除节点
*
* @param node
*/
private void removeNode(DoubleNode<K, V> node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
/**
* 将新节点添加到链表头部
* @param newNode
*/
public void addNodeToHead(DoubleNode<K,V> newNode){
newNode.next = headNode.next;
newNode.prev=headNode;
headNode.next.prev=newNode;
headNode.next = newNode;
}
/**
* 从散列表获取节点值
* @param key
* @return
*/
public V get(K key){
DoubleNode<K,V> node = table.get(key);
if (node == null) {
return null;
}
//节点存在移动到头部
moveToHead(node);
return node.value;
}
/**
* 从散列移除节点数据
*
* @param key
*/
public void remove(K key) {
DoubleNode<K, V> node = table.get(key);
if (node == null) {
return;
}
//节点存在则移除此节点
removeNode(node);
length--;
}
private void printAll() {
DoubleNode<K, V> node = headNode.next;
while (node.next != null) {
System.out.print(node.value + ",");
node = node.next;
}
System.out.println();
}
}