基于单向链表实现LRU缓存淘汰算法

准备工作

思考:链表是由一个一个结点单向连接而成,因此我们需要创建一个结点类,该类包含结点数据,以及下一个结点的位置信息!

一、结点类:

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缓存淘汰算法实现!

代码中有很多写的不好的地方,欢迎留言讨论

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值