算法 | 链表的应用,缓存失效算法

我们知道,缓存是一种提高数据读取性能的技术,在硬件涉及,软件开发中有着非常广泛的应用。在使用缓存时我们最大的问题就是缓存的数据是不能太多的,当缓存被占满时,我们如何决定哪些数据被清除,哪些数据被保留呢?这个时候,我们就会采用一些缓存失效算法来处理。今天这篇文章我们就一起学习下常见的缓存失效算法通过链表如何实现。

先进先出策略(FIFO,First In First Out)

这种策略的实现是最简单的了,我们利用链表的特性,只要将需要缓存的数据依次添加到链表中,当空间不够时,将链表末尾的数据删除即可(如果我们对队列比较熟悉的话,可以发现这种策略用队列进行实现是最方便的)

实现代码如下:

package com.study.spring.transaction.cache;

import lombok.AllArgsConstructor;

import java.security.InvalidParameterException;

/**
 * @author dmz
 * @date Create in 20:57 2019/8/10
 */
public class Main {
    public static void main(String[] args) {
        link link = new link(4);
        link.add(1);
        link.add(2);
        link.add(3);
        link.add(4);
        link.add(5);
        link.add(6);
        link.add(7);
        link.sout();
    }

    static class link {
        public link(int max) {
            if (max > 0) {
                this.max = max;
            } else {
                throw new InvalidParameterException("参数不能小于0");
            }
        }

        // 最大能添加的元素的个数
        int max;
        // 链表中实际已经存储的元素的个数
        int size;
        // 链表的头节点
        Node first;
        // 链表的尾节点
        Node last;

        // 链表中的每个节点
        @AllArgsConstructor
        class Node {
            // 节点中保存的数据
            Object item;
            // 指向上一个节点
            Node pre;
            // 指向下一个节点
            Node next;
        }

        public void add(Object item) {
            Node node = new Node(item, null, null);
            if (size == 0) {
                size++;
                node.pre = null;
                node.next = null;
                first = node;
                last = node;
            } else {
                if (size == max) {
                    // 链表中的数据已经满了
                    // 策略1:先进先出,需要删除头节点
                    
                    Node second = first.next;
                    second.pre = null;
                    first.next = null;
                    first = second;
                    size--;
                }
                // 将数据添加到链表尾部
                size++;
                last.next = node;
                node.pre = last;
                last = node;
            }
        }

        public void sout() {
            Node node = first;
            for (int i = 0; i < max; i++) {
                if (size == 0) {
                    System.out.println("链表中没有元素");
                    break;
                } else {
                    System.out.println(node.item);
                    node = node.next;
                    if (node == null) {
                        System.out.println("遍历结束");
                        break;
                    }
                }
            }
        }
    }

}

输出结果如下:

4
5
6
7
最少使用策略(LFU,Least Frequently Out)

根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。

下面给出两种实现方式

JDK自带优先级队列进行实现

实现思路:利用PriorityQueue小顶堆的特点,关于PriorityQueue可参考我之前的文章 java读源码 之 queue源码分析(PriorityQueue,附图)。每次出队的一定是优先级最低的元素,同时我们自定义比较器,我这里是通过用实体实现Comparable接口的方式。

创建类如下:

package com.study.spring.transaction.huancun;

import java.time.LocalDateTime;

/**
 * 进行一个简单的抽象,所有实体都要通过访问时间跟访问次数进行比较
 * @author dmz
 * @date Create in 23:45 2019/8/10
 */
public interface Count extends Comparable<Count> {

    void setLastVisitTime(LocalDateTime localDateTime);

    LocalDateTime getLastVisitTime();

    int getCount();

    void setCount(int count);

    default void addCount() {
        setCount(getCount() + 1);
    }

    // 根据访问次数比较,次数相同的情况下比较访问时间
    @Override
    default int compareTo(Count o2) {
        return this.getCount() == o2.getCount() ? this.getLastVisitTime().compareTo(o2.getLastVisitTime())
                : this.getCount() - o2.getCount();
    }
}

package com.study.spring.transaction.huancun;

import lombok.Data;

import java.time.LocalDateTime;

/**
 * @author dmz
 * @date Create in 23:45 2019/8/10
 */
@Data
public class Entity implements Count, Comparable<Entity> {

    private int count;

    private Object object;

    private LocalDateTime lastVisitTime = LocalDateTime.now();

    Entity(Object object) {
        this.object = object;
    }	
}

package com.study.spring.transaction.huancun;

import java.time.LocalDateTime;
import java.util.PriorityQueue;

/**
 * @author dmz
 * @date Create in 23:55 2019/8/10
 */
public class LFUStrategy<T extends Count> {

    private int max;

    LFUStrategy(PriorityQueue<T> priorityQueue, int max) {
        this.max = max;
        this.priorityQueue = priorityQueue;
    }

    private PriorityQueue<T> priorityQueue;

    /**
     * 访问排列在index位置的元素
     *
     * @param index 位置
     * @return 所在元素
     */
    public T get(int index) {
        int count = 1;
        for (T t : priorityQueue) {
            if (count == index) {
                priorityQueue.remove(t);
                t.addCount();
                t.setLastVisitTime(LocalDateTime.now());
                // 先remove再add是为了利用其内部特性,调用add方法时会进行排序,将最小的元素置于堆顶
                priorityQueue.add(t);
                return t;
            }
        }
        return null;
    }

    @Override
    public String toString() {
        return "LFUStrategy{" +
                "priorityQueue=" + priorityQueue +
                '}';
    }

    public T remove() {
        return priorityQueue.poll();
    }

    public void add(T t) {
        if (max == priorityQueue.size()) {
            T remove = remove();
            System.out.println("执行LFU策略,删除的元素是:" + remove);
        } else {
            priorityQueue.offer(t);
        }
    }
}
package com.study.spring.transaction.huancun;

import java.util.PriorityQueue;

/**
 * @author dmz
 * @date Create in 0:08 2019/8/11
 */
public class Main {
    public static void main(String[] args) {
        PriorityQueue<Entity> priorityQueue = new PriorityQueue<>();
        for (int i = 1; i < 7; i++) {
            Entity entity = new Entity(i);
            priorityQueue.add(entity);
            try {
                // 为了将访问时间隔开
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 最大容忍6个元素,元素满了后,执行LFU策略
        LFUStrategy lfuStrategy = new LFUStrategy<>(priorityQueue, 6);
        // 对第一个元素进行访问
//        lfuStrategy.get(1);
        lfuStrategy.add(new Entity(7));
    }
}

在上述代码中,如果我们不访问任何元素,直接执行 lfuStrategy.add(new Entity(7)),那么就会直接移除第一个元素,但是如果我们访问第一个元素,那么就会移除第二个元素。大家可以自己试验,我就不贴运行结果了,太占篇幅

链表实现

实现思路:相比于上一种实现方式,这里最大的不同就是要自己去实现排序的过程。我们还是直接用JDK中的LinkedList,进行实现。

package com.study.spring.transaction.huancun;

import lombok.Data;
import lombok.RequiredArgsConstructor;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.LinkedList;

/**
 * @author dmz
 * @date Create in 12:03 2019/8/11
 */
@Data
@AllArgsConstructor
public class LFUStrategySecond<T extends Count> {
    private int max;
    private LinkedList<T> linkedList;

    public void add(T t) {
        if (linkedList.size() == max) {
            System.out.println("链表实现LFU策略删除元素:" + remove());
        }
        linkedList.add(t);
    }

    public T remove() {
        return linkedList.removeFirst();
    }

    public T get(int index) {
        T t = linkedList.get(index);
        t.addCount();
        t.setLastVisitTime(LocalDateTime.now());
        // 最小的放到链表头
        linkedList.sort((Comparator<T>) (o1, o2) ->
                o1.getCount() == o2.getCount() ? o1.getLastVisitTime().compareTo(o2.getLastVisitTime())
                        : o1.getCount() - o2.getCount()
        );
        return t;
    }
}


ackage com.study.spring.transaction.huancun;

import java.util.LinkedList;

/**
 * @author dmz
 * @date Create in 12:10 2019/8/11
 */
public class MainSecond {
    public static void main(String[] args) {

        LinkedList<Entity> linkedList = new LinkedList<>();
        linkedList.add(new Entity(1));
        linkedList.add(new Entity(2));
        linkedList.add(new Entity(3));
        linkedList.add(new Entity(4));
        linkedList.add(new Entity(5));
        LFUStrategySecond<Entity> lfuStrategySecond = new LFUStrategySecond<Entity>(5, linkedList);
        // 可以观察,进行访问跟不进行访问,删除元素的区别
  //      lfuStrategySecond.get(0);
        lfuStrategySecond.add(new Entity(6));
    }
}
最近最少使用策略(LRU,Least Recently Used)

根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

实现思路:我们还是直接利用JDK的Linkedlist来进行实现,只要每次将被访问的元素置于链尾,若空间不够堆链头的数据进行删除即可。

package com.study.spring.transaction.huancun;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.LinkedList;

/**
 * @author dmz
 * @date Create in 12:25 2019/8/11
 */
@Data
@AllArgsConstructor
public class LRUStrategy<T> {
    // 不能小于1
    int max;

    LinkedList<T> linkedList = new LinkedList<>();

    public T get(int index) {
        // 每次获取元素时,将获取到的元素放到链尾
        T t = linkedList.get(index);
        linkedList.remove(t);
        linkedList.addLast(t);
        return t;
    }

    public T remove() {
        return linkedList.removeFirst();
    }

    public void add(T t) {
        if (linkedList.size() == max) {
            System.out.println("LRU删除的元素:"+remove());
        }
        linkedList.add(t);
    }
}

总结:

到这里三种缓存过期策略我们就介绍完了,可能对于最后一种LRU算法介绍的不是很详细,本来是想自定义一个单链表来实现的,不过这两天写链表都写吐了,实在不想写了,偷了个懒,大家可以自己实现下,有任何问题都可以给我留言哦,一定会回复的!

码字不易,喜欢的小伙伴点个赞,加个关注吧,万分感谢,你的支持就是我写作的最大动力!

数据结构与算法|目录汇总

posted @ 2019-08-11 12:42  明智说  阅读(169)  评论(0编辑  收藏  举报