单链表实现LRU淘汰策略中缓存调入功能
实现思路
以自定义结点作为缓存当中的内容,进行两步判断:
- 判断要调入缓存的内容是否已经存在于缓存当中
– 如果已经存在,则从原本位置摘下此结点,以头插法加进链表
– 如果不存在于链表当中,则需要新增进链表,此时需判满
2.在要调入内容不存在于缓存的情况下,判断当前缓存是否已满:
– 如果没有满,则直接头插法加入;
–如果缓存已满,需要从链表最后摘下一个结点,腾出空间之后,头插法加入新结点
具体实现
- 自定义结点结构以及定义缓存大小
缓存通过静态成员变量存储,记录当前尚余的缓存空间。
public static int size = 100
class Node {
//数据域与指针域
int value;
Node next;
//单参/双参构造方法
public Node(int value) {
this.value = value;
}
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
//重写toString以便验证结果
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
}
- 判断该结点是否处于当前内存当中
通过遍历即可实现,如果存在此结点就返回其前驱结点,如果不存在则返回null;
因为此处是单链表实现,在判断之后需要进行摘链等操作,为避免找前驱结点再次进行遍历,直接返回的是前驱结点。头结点由于没有前驱,单独进行判断。
public static Node findPrevious(Node head, Node node) {
//有node就返回前驱,没有就返回null,head单独判断
Node x = head;
if (head == node) {
return head;
} else {
while (x.next != null) {
//data域相同的结点认为相同
if (x.next.value == node.value) return x;
x = x.next;
}
}
return null;
}
}
- 将移除尾部结点定义成一个方法
遍历到链表最后并摘下最后一个结点
public static void removeTailNode(Node head) {
Node x = head;
while (x.next.next != null) {
x = x.next;
}
//删除尾部节点
x.next = null;
}
4.实现整个新增逻辑
要注意的是,头插之后产生的是个新的链,如果此方法返回值为void,那么在调用处得到的不是我们改动之后的链。
public static Node insert(Node head, Node node) {
//向链表head中添加node结点
//判存在的同时取其前驱
Node pre = findPrevious(head, node);
if (pre != null) {
//该结点存在,先删再头插
pre.next = pre.next.next;
node.next = head;
head = node;
} else {
//如果不存在于链表当中,则进行判满及后续处理
if (size == 0) {
//摘下最后结点
removeTailNode(head);
//头插
node.next = head;
head = node;
} else {
//头插
node.next = head;
head = node;
size--;
}
}
return head;
}
5.测试代码
public static void main(String[] args) {
Node node = new Node(101);
Node node1 = new Node(36);
Node node2 = new Node(100);
//头插法创建99个结点
Node head = null;
for (int i = 1; i <= 99; i++) {
head = new Node(i, head);
(size)--;
}
//打印刚创建好的链表
printList(head);
//调入36结点
head = insert(head, node1);
printList(head);
//调100,101结点
head = insert(head, node2);
head = insert(head, node);
printList(head);
}
6.运行效果
双向链表实现LRU淘汰策略中缓存调入功能
实现思路与单链表相同,唯一不同的是对于结点的处理
具体实现
1.结点类定义
/*自定义双链表结点*/
class Node<T>{
//泛型保证数据域数据的灵活性
T data;
Node pre;
Node next;
public Node(T data) {
this.data = data;
}
public Node(T data, Node pre, Node next) {
this.data = data;
this.pre = pre;
this.next = next;
}
//方便显示
@Override
public String toString() {
return "{" + data +'}';
}
//方便比较结点内容
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Node)) return false;
Node<?> node = (Node<?>) o;
return Objects.equals(data, node.data);
}
}
2.封装的LRU类
- 将首尾结点,缓存容量,添加结点的方法都封装在类中提供
- 其中频繁使用的头插/移除末尾结点的操作,封装成方法进行调用。
- 添加首尾结点是为了保证操作的一致性。
class LRU {
private int size=100;
private Node head=new Node("head");
private Node tail=new Node("end");
public LRU(int size) {
//创建的时候就必须指定缓存大小
this.size = size;
//创建的两个边界节点首先接上链,才能够保证操作的一致性
this.getHead().next=this.getTail();
}
public Node getHead() {
return head;
}
public Node getTail() {
return tail;
}
public int getSize() {
return size;
}
public void addNode(Node node){
//判断是否存在
Node x=isExsist(node);
if(x!=null){
//如果该节点存在于当前缓存当中,删了再头插
removeNode(x);
insertAtFirst(x);
}else{
//如果该结点不存在于当前缓存中,判满
if(size!=0){
//没满直接头插,内存容量-1
//这里用node操作不要用x,x如果被判空会变为null
insertAtFirst(node);
}else{
//满了,删掉最后一个结点再头插
removeNode(tail.pre);
insertAtFirst(node);
}
}
}
public void insertAtFirst(Node node){
//将node结点头插在head结点之后
node.next=head.next;
node.pre=head;
node.pre.next=node;
node.next.pre=node;
size--;
}
public void removeNode(Node node){
//摘下指定结点node,要删第一个结点传head.next,删最后一个结点传tail.pre
node.next.pre = node.pre;
node.pre.next = node.next;
//结点数量管理
size++;
/* node.next = null;
node.pre = null;*/
}
public Node isExsist(Node node){
//判断当前链表当中是否存在该结点,存在就返回该结点,不存在就返回null
//直接正向遍历并寻找该结点
Node x=head.next;
while(x!=tail){
//如果存在内容一样的,返回结点当前的位置
//node.data==x.data,这里用==的话会造成比较的是左右两个对象的地址,同样的字符串可能因地址不同被插进去
if(node.equals(x)) return x;
x=x.next;
}
//如果遍历了都没找到,返回null
return null;
}
public void print(Node head){
//从头打印链表
Node x=head;
while(x!=tail){
System.out.print(x+"--");
x=x.next;
}
System.out.println("{tail}");
}
}
- 测试类
public static void main(String[] args) {
//新建对象并设定缓存大小
LRU lru=new LRU(10);
for(int i=0;lru.getSize()>0;i++){
lru.insertAtFirst(new Node(i));
}
//打印原链表
lru.print(lru.getHead());
//新建结点
Node node1=new Node(5);
Node node2=new Node("hello");
lru.addNode(node1);
lru.print(lru.getHead());
lru.addNode(node2);
lru.print(lru.getHead());
}
4.运行结果