准备工作
思考:链表是由一个一个结点单向连接而成,因此我们需要创建一个结点类,该类包含结点数据,以及下一个结点的位置信息!
一、结点类:
package com.linkTest;
public class Node<E> {
private Node<E> next;//指向下一个节点
private E e;//存储数据
public Node(E e){
this.e = e;
this.next = null;
}
public Node(){
this.e = null;
this.next = null;
}
public Node<E> getNext() {
return next;
}
public void setNext(Node<E> next) {
this.next = next;
}
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
@Override
public String toString() {
return "Node{" +
"next=" + next +
", e=" + e +
'}';
}
}
在结点类中我又加入了toString方法,方便我们查看结点数据!
二、单向链表类
我们要将这个结点串起来,实现单向链表结构!
package com.linkTest;
import java.util.Objects;
public class LinkNode<E> {
private Node head;//定义一个头结点
private Node<E> last;//声明为节点
private int size;//链表大小
/**
* 构造方法,因为没有添加数据时,头结点就是尾结点
*/
public LinkNode(){
this.head = new Node();
last=head;//令头节点等于尾节点
}
/**
* 获取单项链表的长度
* @return
*/
public int getSize(){
return size;
}
/**
* 向单项链表中添加数据
* @param e
*/
public void add(E e){
Node<E> node = new Node<>(e);//实例化一个节点
last.setNext(node);//往尾节点后加一个节点
last = node;//将该节点设为尾节点
size++;//链表长度加1
}
/**
* 将数据添加到第一位
* 如果缓存中包含e则将原数据删除
* 如果缓存中不包含e,则删除最后一个结点
* @param e
*/
public void addFirstOrRemove(E e){
//初始化一个Node结点
Node<E> node = new Node<>(e);
int i = isContain(e);
if(i == -1){
//将数据插入第一个节点
Node<E> first = head.getNext();
node.setNext(first);
head.setNext(node);
//移除最后一个节点
Node<E> select = select(size - 1);
select.setNext(null);
last = select;
}else{
//将数据插入第一个节点
Node<E> first = head.getNext();
node.setNext(first);
head.setNext(node);
//将之前的节点删除
Node<E> select = select(i);
Node〈E〉select2= select.getNext();
Node<E> select1 = select2.getNext();
select.setNext(select1);
select2.setNext(null);
}
}
/**
* 判断是否包含数据
* 包含则返回数据节点位置
* 不包含则返回-1
* @param e
* @return
*/
public int isContain(E e){
for(int i = 0; i < size; i ++){
Node<E> select = select(i);
if(Objects.equals(select.getE(),e)){
return i;
}
}
return -1;
}
/**
* 删除指定位置的数据
* @param index
* @return
*/
public E delete(int index){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("索引位置越界!不允许操作!");
}
if(index == 0){
Node<E> node = head.getNext();
head.setNext(node.getNext());
size--;
return node.getE();
}
Node<E> node = select(index - 1);//获取删除节点的前一个节点
Node<E> nextNode = node.getNext();//获取要删除的节点
Node<E> nextNextNode = nextNode.getNext();//获取删除节点的下一个节点
node.setNext(nextNextNode);//将删除节点的前一个节点指向删除节点的后一个节点
node.setNext(null);//将删除节点的下一个节点设为null
return nextNode.getE();//返回删除节点的信息
}
/**
* 查找指定位置的节点对象
* @param index
* @return
*/
public Node<E> select(int index){
Node<E> node = head.getNext();
for(int i = 0;i < index;i ++){
node = node.getNext();
}
return node;
}
/**
* 获取指定位置的数据
* @param index
* @return
*/
public E getE(int index){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("索引位置越界!不允许操作!");
}
Node<E> select = select(index);
return select.getE();
}
/**
* 修改指定位置的数据,并返回原数据!
*/
public E CoverE(E e,int index){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("索引位置越界!不允许操作!");
}
Node<E> node = select(index);
E before = node.getE();
node.setE(e);
return before;
}
@Override
public String toString() {
return "LinkNode{" +
"head=" + head +
", size=" + size +
'}';
}
}
这段代码包含了很多方法,其中对包含实现LRU算法非常重要的方法,addFirstOrRemove(E e),此方法也是LRU缓存淘汰算法的核心:
缓存未满 -------则直接将数据加入到缓存中。
缓存满了
---------如果缓存中包含此数据,则将数据移到头结点。
---------如果缓存中不包含此数据,则直接将数据添加到头结点,并且删除最后一个结点!
三、创建缓存类
package com.linkTest;
import java.util.Objects;
public class LruCache<E> {
private LinkNode<E> linkNode;//存储数据的位置
private int max;//Lru缓存容量
private int length;//当前缓存的数量
/**
* 初始化方法
* @param max
*/
public LruCache(int max){
this.length = 0;
this.max = max;
linkNode = new LinkNode<>();
}
public int getLength(){
return this.length;
}
/**
* 获取缓存中的值
* @return
*/
public E get(E e){
for(int i = 0; i < linkNode.getSize(); i ++){
E s = linkNode.getE(i);
if(Objects.equals(s,e)){
return s;
}
}
return null;
}
/**
* 向缓存中添加数据!
* @param e
*/
public void add(E e){
if(linkNode.getSize() < max){
linkNode.add(e);
length ++;
}else {
linkNode.addFirstOrRemove(e);
}
}
@Override
public String toString() {
return "LruCache{" +
"linkNode=" + linkNode +
'}';
}
}
这个类中我也添加了toString() 方法,是为了方便我们查看缓存中的数据!
测试
package com.linkTest;
public class TestLruNode {
public static void main(String[] args) {
//初始一个缓存链表
LruCache<Student> lruCache = new LruCache<>(5);
lruCache.add(new Student("张三",24));
System.out.println("------------第一次添加数据之后-------");
System.out.println(lruCache);
System.out.println("缓存的长度:" + lruCache.getLength());
lruCache.add(new Student("李四",24));
System.out.println("------------第二次添加数据之后-------");
System.out.println(lruCache);
System.out.println("缓存的长度:" + lruCache.getLength());
lruCache.add(new Student("王五",24));
System.out.println("------------第三次添加数据之后-------");
System.out.println(lruCache);
System.out.println("缓存的长度:" + lruCache.getLength());
lruCache.add(new Student("赵六",24));
System.out.println("------------第四次添加数据之后-------");
System.out.println(lruCache);
System.out.println("缓存的长度:" + lruCache.getLength());
lruCache.add(new Student("七七",24));
System.out.println("------------第五次添加数据之后-------");
System.out.println(lruCache);
System.out.println("缓存的长度:" + lruCache.getLength());
lruCache.add(new Student("八八",24));
System.out.println("------------第六次添加数据之后-------");
System.out.println(lruCache);
System.out.println("缓存的长度:" + lruCache.getLength());
lruCache.add(new Student("王五",24));
System.out.println("------------第七次添加数据之后-------");
System.out.println(lruCache);
System.out.println("缓存的长度:" + lruCache.getLength());
}
}
我将打印信息也粘贴下来方便理解
------------第一次添加数据之后-------
LruCache{linkNode=LinkNode{head=Node{next=Node{next=null, e=Student{name='张三', age=24}}, e=null}, size=1}}
缓存的长度:1
------------第二次添加数据之后-------
LruCache{linkNode=LinkNode{head=Node{next=Node{next=Node{next=null, e=Student{name='李四', age=24}}, e=Student{name='张三', age=24}}, e=null}, size=2}}
缓存的长度:2
------------第三次添加数据之后-------
LruCache{linkNode=LinkNode{head=Node{next=Node{next=Node{next=Node{next=null, e=Student{name='王五', age=24}}, e=Student{name='李四', age=24}}, e=Student{name='张三', age=24}}, e=null}, size=3}}
缓存的长度:3
------------第四次添加数据之后-------
LruCache{linkNode=LinkNode{head=Node{next=Node{next=Node{next=Node{next=Node{next=null, e=Student{name='赵六', age=24}}, e=Student{name='王五', age=24}}, e=Student{name='李四', age=24}}, e=Student{name='张三', age=24}}, e=null}, size=4}}
缓存的长度:4
------------第五次添加数据之后-------
LruCache{linkNode=LinkNode{head=Node{next=Node{next=Node{next=Node{next=Node{next=Node{next=null, e=Student{name='七七', age=24}}, e=Student{name='赵六', age=24}}, e=Student{name='王五', age=24}}, e=Student{name='李四', age=24}}, e=Student{name='张三', age=24}}, e=null}, size=5}}
缓存的长度:5
------------第六次添加数据之后-------
LruCache{linkNode=LinkNode{head=Node{next=Node{next=Node{next=Node{next=Node{next=Node{next=null, e=Student{name='赵六', age=24}}, e=Student{name='王五', age=24}}, e=Student{name='李四', age=24}}, e=Student{name='张三', age=24}}, e=Student{name='八八', age=24}}, e=null}, size=5}}
缓存的长度:5
------------第七次添加数据之后-------
LruCache{linkNode=LinkNode{head=Node{next=Node{next=Node{next=Node{next=Node{next=Node{next=null, e=Student{name='赵六', age=24}}, e=Student{name='李四', age=24}}, e=Student{name='张三', age=24}}, e=Student{name='八八', age=24}}, e=Student{name='王五', age=24}}, e=null}, size=5}}
缓存的长度:5
Process finished with exit code 0
从缓存长度为5之后,我分别添加了两种类型的数据,
第一种类型数据是:新数据,缓存中不包含的数据!
从打印结果看,符合我们的预期!
第二种类型数据是:老数据,缓存中已包含的数据!
从打印结果看,符合我们的预期!
至此、使用单向链表实现LRU缓存淘汰算法实现!
代码中有很多写的不好的地方,欢迎留言讨论