数据结构与算法的深入应用-淘汰算法

前言:

淘汰算法常见于缓存系统,因为缓存往往占据大量的内存,而内存是比较昂贵的资源,一般不可能将全部的数据放置于缓存,因此我们需要对缓存中的数据进行淘汰。常见的淘汰算法有以下三种:

1. FIFO(先来先淘汰)

代码实现:

package com.xieq.algorithm.realease;

import java.util.Collections;
import java.util.LinkedList;

/**
 * 先来先淘汰:
 * 优点:实现简单
 * 缺点:不管元素的使用情况,总是淘汰先缓存进来的元素
 *
 * @author qiang.xie02
 * @date 2022/1/14 15:05
 */
public class FIFO {
    private static final Integer size = 3;
    private static final LinkedList<Integer> list = new LinkedList<>();

    private void add(Integer i) {
        if (list.size() >= size) {
            list.removeFirst();
        }
        list.add(i);
        printFifo();
    }

    private static void printFifo() {
        System.out.println(Collections.singletonList(list));
    }

    private void read(Integer i) {
        for (Integer next : list) {
            if (next.equals(i)) {
                System.out.println("read " + i + "读到了缓存");
                return;
            }
        }
        System.out.println("read " + i + "缓存中没有");
    }

    public static void main(String[] args) {
        FIFO fifo = new FIFO();
        fifo.add(1);
        fifo.add(2);
        fifo.add(3);

        fifo.read(2);
        fifo.read(100);

        fifo.add(4);
        fifo.add(50);
        fifo.add(90);
    }
}

运行输出如下:

[[1]]
[[1, 2]]
[[1, 2, 3]]
read 2读到了缓存
read 100缓存中没有
[[2, 3, 4]]
[[3, 4, 50]]
[[4, 50, 90]]

可见不断添加元素,淘汰的总是最先加入进缓存的元素。
总结:fifo淘汰算法实现简单,但过于简单粗暴,总是淘汰最先缓存的元素。

2,LRU(最久未使用淘汰)

最久未使用淘汰 Last Recently used (LRU) 即淘汰使用时间最久的元素。因为FIFO非常的粗暴(直接淘汰最先缓存起来的元素,不管元素的使用情况) 而LRU认为当前使用频率较高的元素在将来也有极大的可能继续使用,故而淘汰最最久未读取的元素;
1,新增元素时新增到头部
2,读取元素时将元素移动到头部
3,淘汰时淘汰最后一个元素

package com.xieq.algorithm.realease;

import java.util.Collections;
import java.util.LinkedList;

/**
 * 最久未使用淘汰
 * Last Recently used (LRU) 即淘汰最近使用时间最久的元素。因为FIFO非常的粗暴(直接淘汰最先缓存起来的元素,不管元素的使用情况)
 * 而LRU认为当前使用频率较多的元素在将来也有极大的可能继续使用,故而淘汰最近使用最久的元素;
 * 1.新增元素时新增到头部
 * 2,读取元素时将元素移动到头部
 * 3,淘汰时淘汰最后一个元素
 *
 * @author qiang.xie02
 * @date 2022/1/14 15:35
 */
public class LRU {
    private static final Integer size = 3;
    private static final LinkedList<Integer> list = new LinkedList();

    public void add(Integer i) {
        if (list.size() >= size) {
            list.removeLast();
        }
        list.add(i);
        System.out.println(Collections.singletonList(list));
    }

    public void read(Integer i) {
        for (int j = 0; j < list.size(); j++) {
            Integer el = list.get(j);
            if (el.equals(i)) {
                System.out.println("read" + i + "from cache");
                list.remove(j);
                list.addFirst(i);
                System.out.println(Collections.singletonList(list));
                return;
            }
        }
        System.out.println("read" + i + "缓存中没有");
        System.out.println(Collections.singletonList(list));
    }

    public static void main(String[] args) {
        LRU lru = new LRU();
        System.out.println("add 1-3:");
        lru.add(1);
        lru.add(2);
        lru.add(3);

        System.out.println("read 2:");
        lru.read(2);
        System.out.println("read 4:");
        lru.read(4);
        System.out.println("read 3:");
        lru.read(3);
        System.out.println("read 3:");
        lru.read(3);

        System.out.println("add 5:");
        lru.add(5);
        System.out.println("add 6:");
        lru.add(6);
        System.out.println("add 7:");
        lru.add(7);
        System.out.println("add 8:");
        lru.add(8);
        System.out.println("read 8:");
        lru.read(8);
        System.out.println("read 8:");
        lru.read(8);
        lru.add(9);
        System.out.println("read 9:");
        lru.read(9);
        System.out.println("read 9:");
        lru.read(9);
        System.out.println("add 10:");
        lru.add(10);

        System.out.println("read 10:");
        lru.read(10);
        System.out.println("read 10:");
        lru.read(10);


    }
}

运行输出如下:

add 1-3:
[[1]]
[[1, 2]]
[[1, 2, 3]]
read 2:
read2from cache
[[2, 1, 3]]
read 4:
read4缓存中没有
[[2, 1, 3]]
read 3:
read3from cache
[[3, 2, 1]]
read 3:
read3from cache
[[3, 2, 1]]
add 5:
[[3, 2, 5]]
add 6:
[[3, 2, 6]]
add 7:
[[3, 2, 7]]
add 8:
[[3, 2, 8]]
read 8:
read8from cache
[[8, 3, 2]]
read 8:
read8from cache
[[8, 3, 2]]
[[8, 3, 9]]
read 9:
read9from cache
[[9, 8, 3]]
read 9:
read9from cache
[[9, 8, 3]]
add 10:
[[9, 8, 10]]
read 10:
read10from cache
[[10, 9, 8]]
read 10:
read10from cache
[[10, 9, 8]]

3,LFU(最近最少使用淘汰)

最近最少使用在LRU的基础上新增时间的维度,即需要从最近的使用时间和使用的次数两个维度来淘汰元素,因此需要一个数据模型来记录从这两个维度来记录缓存元素的读取信息, 定义如下dto来记录缓存元素的读取信息(最近的读取时间与读取次数)

package com.xieq.algorithm.realease;

/**
 * Description
 *
 * @author qiang.xie02
 * @date 2022/1/14 16:07
 */
public class Dto implements Comparable<Dto> {

    private Integer key;
    private Integer count;
    private Long lastTime;

    public Dto(Integer key, Integer count, Long lastTime) {
        this.key = key;
        this.count = count;
        this.lastTime = lastTime;
    }

    public Dto() {
    }

    @Override
    public int compareTo(Dto o) {
        int countCompare = this.count.compareTo(o.getCount());
        return countCompare == 0 ? this.getLastTime() > o.getLastTime() ? 1 : 0 : countCompare;
    }

    @Override
    public String toString() {
        return "Dto{" +
                "key=" + key +
                ", count=" + count +
                ", lastTime=" + lastTime +
                '}';
    }

    public Integer getKey() {
        return key;
    }

    public void setKey(Integer key) {
        this.key = key;
    }

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public Long getLastTime() {
        return lastTime;
    }

    public void setLastTime(Long lastTime) {
        this.lastTime = lastTime;
    }
}

package com.xieq.algorithm.realease;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 最近最少使用淘汰
 *
 * @author qiang.xie02
 * @date 2022/1/14 16:05
 */
public class LFU {

    private static final Integer SIZE = 3;
    private static final HashMap<Integer, Integer> CACHE = new HashMap<>();
    private static final Map<Integer, Dto> COUNTS = new HashMap<>();

    public void add(Integer k, Integer v) {
        if (CACHE.size() >= SIZE) {
            removeEle();
        }
        CACHE.put(k, v);
        Dto dto = COUNTS.get(k);
        if (dto != null) {
            COUNTS.put(k, new Dto(k, dto.getCount() + 1, System.currentTimeMillis()));
        } else {
            COUNTS.put(k, new Dto(k, 1, System.currentTimeMillis()));
        }
        printCache();
    }

    public Integer read(Integer k) {
        Integer v = CACHE.get(k);
        Dto dto = COUNTS.get(k);
        COUNTS.put(k, new Dto(k, dto.getCount() + 1, System.currentTimeMillis()));
        printCache();
        return v;
    }

    private void removeEle() {
        Dto min = Collections.min(COUNTS.values());
        CACHE.remove(min.getKey());
        COUNTS.remove(min.getKey());
    }

    void printCache() {
        Set<Map.Entry<Integer, Integer>> entries = CACHE.entrySet();
        System.out.println(COUNTS);
    }

    public static void main(String[] args) {
        LFU lfu = new LFU();
        System.out.println("add key 1-3");
        lfu.add(1, 1);
        lfu.add(2, 2);
        lfu.add(3, 3);

        System.out.println("read key 2");
        lfu.read(2);

        System.out.println("read key 3");
        lfu.read(3);

        System.out.println("add key 4");
        lfu.add(4, 4);

        System.out.println("read key 3");
        lfu.read(3);
        System.out.println("read key 4");
        lfu.read(4);
        System.out.println("add key 3");
        lfu.add(5,5);
    }
}

运行输出如下,可见是从两个维度(时间与使用次数)进行元素的淘汰:

add key 1-3
{1=Dto{key=1, count=1, lastTime=1642152092234}}
{1=Dto{key=1, count=1, lastTime=1642152092234}, 2=Dto{key=2, count=1, lastTime=1642152092234}}
{1=Dto{key=1, count=1, lastTime=1642152092234}, 2=Dto{key=2, count=1, lastTime=1642152092234}, 3=Dto{key=3, count=1, lastTime=1642152092235}}
read key 2
{1=Dto{key=1, count=1, lastTime=1642152092234}, 2=Dto{key=2, count=2, lastTime=1642152092235}, 3=Dto{key=3, count=1, lastTime=1642152092235}}
read key 3
{1=Dto{key=1, count=1, lastTime=1642152092234}, 2=Dto{key=2, count=2, lastTime=1642152092235}, 3=Dto{key=3, count=2, lastTime=1642152092235}}
add key 4
{2=Dto{key=2, count=2, lastTime=1642152092235}, 3=Dto{key=3, count=2, lastTime=1642152092235}, 4=Dto{key=4, count=1, lastTime=1642152092235}}
read key 3
{2=Dto{key=2, count=2, lastTime=1642152092235}, 3=Dto{key=3, count=3, lastTime=1642152092235}, 4=Dto{key=4, count=1, lastTime=1642152092235}}
read key 4
{2=Dto{key=2, count=2, lastTime=1642152092235}, 3=Dto{key=3, count=3, lastTime=1642152092235}, 4=Dto{key=4, count=2, lastTime=1642152092235}}
add key 3
{3=Dto{key=3, count=3, lastTime=1642152092235}, 4=Dto{key=4, count=2, lastTime=1642152092235}, 5=Dto{key=5, count=1, lastTime=1642152092236}}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值