用Java实现的链表完成简单的LRU缓存淘汰算法

用Java实现的单链表完成简单的LRU缓存淘汰算法

  • 1. 常见三种缓存策略
    • 1.1 为什么使用缓存策略
  • 2. LRU缓存策略
    • 2.1 思路
    • 2.2 代码
    • 2.3 测试
  • 3. 最后

1. 常见三种缓存策略

  • 先进先出策略FIFO(First In,FirstOut)
  • 最少使用策略LFU(Least Frequently Used)
  • 最近最少使用策略LRU(Least Recently Used)

1.1 为什么使用缓存策略

  缓存策略其实就是利用了空间换时间的设计思想。在内存空间相对充足、更要求执行速度的场景中使用。

2. LRU缓存策略

2.1 思路

  维护一个有序的单链表,越靠近尾部的结点是越早之前访问的。当有一个新的数据被访问时,从链表头开始顺序遍历链表。

实现步骤分为两种情况:

  1. 如果新加入的数据已被缓存在链表中,遍历得到数据的结点,将其从原来的结点位置删除,再插入到链表的头部。
  2. 如果新加入的数据没有在链表中,又分为两种情况:
    2.1 如果缓存未满,直接将数据插入到链表的头部;
    2.2 如果缓存已满,删除链表尾结点,将新的数据插入到链表头部;

2.2 代码

  代码部分在我之前写过的文章里进行增量修改。有兴趣的朋友请移步 ☞Java实现数据结构链表之单链表

  添加最大长度属性和构造方法。

private Integer maxLength;

public SingleList(int maxLength) {
        this.maxLength = maxLength;
    }

  添加实现LRU策略的方法。


	/**
     * 功能描述 : LRU
     * @author Leo
     * @date 2020/6/18 10:01 下午
     * @param object
     * @throw
     * @return int
     */
    public int singleListForLRU(T object) {

        //添加之前判断链表中是否存在元素
        int index = this.findNodeIndexByObject(object);

        //如果存在,并且位置是0,不做删除和插入操作
        if (index == 0) {
            return this.length;
        }
        // 如果大于零,删除掉再插入
        if (index > 0) {
            this.delete(index);
        }
        //向链表头部插入元素
        this.insert(object, 0);
        //删除最后多余结点
        if (maxLength!=null&&this.length > maxLength) {
            this.delete(maxLength - 1);
        }
        //最后返回当前链表的长度
        return this.length;
    }
    
	/**
     * 功能描述 : 根据查找的元素返回结点位置 -1 表示查找的对象在链表中不存在
     * @author Leo
     * @date 2020/6/18 10:20 下午
     * @param object
     * @throw
     * @return int
     */
    public int findNodeIndexByObject(T object) {
        int currentIndex = -1;
        if (firstNode == null) {
            return currentIndex;
        } else {
            Node<T> currentNode = this.firstNode;
            int tempIndex = 0;
            do {
                if (currentNode.getObj() == object) {
                    currentIndex = tempIndex;

                }
                currentNode = currentNode.getNext();
                tempIndex++;
            } while (currentNode != null);

            return currentIndex;
        }
    }

2.3 测试

  • 测试代码
public static void main(String[] args){

        SingleList<String> singleList = new SingleList<String>(6);
        singleList.singleListForLRU("零");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("一");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("零");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("二");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("三");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("一");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("二");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("三");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("四");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("五");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("六");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("⑦");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("零");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
        singleList.singleListForLRU("零");
        System.out.println("当前链表长度: "+singleList.length+";当前链表数据: "+singleList.toString());
    }
  • 测试结果

当前链表长度: 1;当前链表数据: SingleList={}
当前链表长度: 2;当前链表数据: SingleList={,}
当前链表长度: 2;当前链表数据: SingleList={,}
当前链表长度: 3;当前链表数据: SingleList={,,}
当前链表长度: 4;当前链表数据: SingleList={,,,}
当前链表长度: 4;当前链表数据: SingleList={,,,}
当前链表长度: 4;当前链表数据: SingleList={,,,}
当前链表长度: 4;当前链表数据: SingleList={,,,}
当前链表长度: 5;当前链表数据: SingleList={,,,,}
当前链表长度: 6;当前链表数据: SingleList={,,,,,}
当前链表长度: 6;当前链表数据: SingleList={,,,,,}
当前链表长度: 6;当前链表数据: SingleList={,,,,,}
当前链表长度: 6;当前链表数据: SingleList={,,,,,}
当前链表长度: 6;当前链表数据: SingleList={,,,,,}

3. 最后

  基于链表数据结构的实现思路,不管缓存有没有满,都需要遍历一遍链表,时间复杂度为O(n)。

  使用其他数据机构可以将时间复杂度O(n)优化到O(1),比如引入散列表(Hash table) 来记录每个数据的位置。

  学习没有捷径,后续还会写关于数据结构与算法的文章,不为装逼,只为学习交流。
           用Java实现的链表完成简单的LRU缓存淘汰算法_第1张图片

你可能感兴趣的:(数据结构与算法)