利用数据结构和算法实现LRU(least recently used)
为什么使用缓存器LRU?
我的理解,CPU内核中存在多级的缓存机制,L1 cache, L2 cache, L3 cache,以及内存memory。
其中CPU读取缓存区cache数据速度比从内存中读取的速度要快近百倍,因此此时在缓存中进行修改,等全部修改完毕以后再写入内存,比每次都从内存中读取,修改完再往内存中写入的速度要更高。
LRU的算法思想
LRU的字面意思为最近最少使用(Least Recently Used)。
其就是将先访问的数据存放到cache头部位置,将不经常使用的数据存放到cache队尾位置,当cache缓存区满时,同时需要新加入元素的时候,就将区尾元素删除,然后将新数据插入到区头位置。
LRU的实现原理
LRU底层是由HashMap + 双向链表组成。
HashMap可以存放键值对,便于快速的定位到需要修改的节点处。查找某节点的时间复杂度为O(1)。
双向链表可以在任意节点处更改此节点前后节点的数据。
算法实现的重点在于
1. 利用HashMap存储键值对,值代表双向链表中的数据节点,键代表每一个数据节点在map中存储的方式
2. 利用双向链表对节点数据进行排序和存储,先进入的数据排于链表头,当数据数值 = 默认数据总量capacity的时候,每新插入一个数据就要删除一个数据。
LRUCache类
1. 包含存储Node节点键值对的HashMap
2. 建立存储Node节点的双向链表(带头节点)
3. 实现方法put(int key, int value); value是存储在Node的数据,key是此Node节点在map中的键。每次put进来数据就需要新建Node节点以及新建HashMap映射。其次判断cache中容量是否越界
4. 实现方法get(int key); 利用key值寻找map映射对应的数据。同时将此Node节点移动至cache头部。
代码实现
包1 主文件
public class Main {
public static void main(String[] args) {
LRUCache cache = new LRUCache(4);
cache.put(1, "A"); // butt
cache.put(2, "B");
cache.put(3, "C");
cache.put(4, "D"); // head
cache.get(1); // A D C B
cache.put(5, "E"); // E A D C
cache.get(2); // 无此数据···
}
}
包2 LRUCache文件
public class LRUCache {
private final int initCapcity;
private final Node<String> cache;
private final HashMap<Integer, Node<String>> map = new HashMap<>();
public LRUCache(int initCapcity){
this.initCapcity = initCapcity;
cache = new Node<String>();
}
// 缓存将数据存储到双向链表中,并将节点位置信息存储到hashmap中
public void put(int key, String data){
Node<String> value = new Node<>(data);
// 如果cache内部超过了能够容纳的数据量则删除LRU元素! -- 链表尾部元素
if(map.size() == initCapcity){
Node<String> delNode = cache;
while (delNode.getNext() != null)
delNode = delNode.getNext();
delNode.getPrior().setNext(null);
delNode.setPrior(null);
// 此处的map根本没有删除掉元素一开始的Node!
for(Integer delKey : map.keySet()){
if(map.get(delKey).equals(delNode)) {
map.remove(delKey);
break;
}
}
}
// 添加元素,添置到链表头上
map.put(key, value);
// 新元素插入,需要设置四条线!
value.setPrior(cache);
value.setNext(cache.getNext());
if(value.getNext() != null)
value.getNext().setPrior(value);
cache.setNext(value);
}
// 利用key以及map函数快速得到节点位置信息,获取数据的时候将其设置到存储链表头部
public void get(int key){
Node<String> modifyNode = map.get(key);
if (modifyNode == null){
System.out.println("没有此数据···");
return;
}
// 修改 此节点需要修改的条数,modify的next 和 prior , 下一个节点的prior 上一个节点的next
// cache节点的next 以及 原首节点的prior
if(modifyNode.getNext() != null)
modifyNode.getNext().setPrior(modifyNode.getPrior());
modifyNode.getPrior().setNext(modifyNode.getNext());
modifyNode.setNext(cache.getNext());
modifyNode.setPrior(cache);
if(modifyNode.getNext() != null)
modifyNode.getNext().setPrior(modifyNode);
cache.setNext(modifyNode);
System.out.println("数据为:" + modifyNode.getData());
}
}
包3 基础组件文件
public class Node<T> {
private T data;
private Node<T> prior;
private Node<T> next;
public Node(T data) {
this.data = data;
this.next = null;
}
public Node() {
this.data = null;
this.next = null;
this.prior = null;
}
public Node(T data, Node<T> prior) {
this.data = data;
this.next = null;
this.prior = prior;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Node<T> getPrior() {
return prior;
}
public void setPrior(Node<T> prior) {
this.prior = prior;
}
public Node<T> getNext() {
return next;
}
public void setNext(Node<T> next) {
this.next = next;
}
}