【算法】缓存淘汰算法

目录

  • 1.概述
  • 2.代码实现
    • 2.1.FIFO
    • 2.2.LRU
    • 2.3.LFU
    • 2.4.Clock
    • 2.5.Random
  • 3.应用

1.概述

缓存淘汰策略是指在缓存容量有限的情况下,当缓存空间不足时决定哪些缓存项应当被移除的策略。缓存淘汰策略的目标是尽可能地保持缓存命中率高,同时合理地利用有限的缓存空间

需要注意的是,下面的代码实现只是对缓存淘汰算法的基本实现,在实际情况中,可以需要考虑更多的因素!

2.代码实现

2.1.FIFO

(1)FIFO (First-In-First-Out) 是一种基本的内存淘汰策略。其思路是按照元素的进入顺序来选择要淘汰的元素。具体来说,当有新的元素要加入到固定容量的缓存中时,如果缓存已满,就需要选择一个元素进行淘汰,以腾出空间存储新的元素。在 FIFO 策略中,选择被缓存时间最长的元素进行淘汰

(2)FIFO 策略维护一个队列,在每次新元素加入缓存时,将新元素添加到队列的末尾。当需要淘汰元素时,选择队列的头部元素作为淘汰对象,即最早进入缓存的元素。通过这种方式,始终保持最早进入缓存的元素在队列头部,最新进入缓存的元素在队列末尾。

(3)使用 FIFO 策略的好处是它的实现简单且执行效率高。然而,它没有考虑元素的访问频率或重要性等因素,只根据进入缓存的顺序来进行淘汰,可能会导致缓存中的数据不够优化。因此,在某些应用场景下,FIFO 策略可能不是最优选择,需要根据实际需求选择更复杂的内存淘汰策略。其具体代码实现如下:

class FIFOCache<K, V> {
    private int capacity;
    private Deque<K> queue;
    private Map<K, V> cache;
	
	//进行初始化操作
    public FIFOCache(int capacity) {
        this.capacity = capacity;
        this.queue = new ArrayDeque<>(capacity);
        this.cache = new HashMap<>(capacity);
    }
	
	//接收一个键 key 并返回相应的值,如果键不存在,则返回 null
    public V get(K key) {
        return cache.getOrDefault(key, null);
    }
	
	//接收一个键 key 和一个值 value,并将它们存储在缓存中
    public void put(K key, V value) {
        if (!cache.containsKey(key)) {
        	//如果缓存已满,将使用队列的 poll 方法移除最早加入的键
            if (queue.size() == capacity) {
                K oldestKey = queue.poll();
                cache.remove(oldestKey);
            }
            //然后将新的键加入队列的尾部
            queue.offer(key);
        }
        //将新的键值对加入缓存
        cache.put(key, value);
    }
	
	public V remove(K key) {
		//从队列中移除指定键
    	queue.remove(key); 
    	// 从缓存中移除指定键并返回对应的值
    	return cache.remove(key); 
	}

	public void clear() {
		//清空队列
	    queue.clear(); 
	    //清空缓存
	    cache.clear(); 
	}
	
	public int size() {
		return cache.size();
	}
}

2.2.LRU

参考 146.LRU 缓存这篇文章。

2.3.LFU

参考 460.LFU 缓存这篇文章。

2.4.Clock

(1)Clock 缓存淘汰算法是一种基于近似“最近未使用” (Not Recently Used) 策略的淘汰算法。该算法通过维护一个环形指针数组 (Clock),来判断缓存项是否被使用,从而进行淘汰决策。Clock 缓存淘汰算法的思路如下:

  • 对于每个缓存项,维护一个额外的访问位来标记缓存项是否被访问过。
  • 初始状态下,将所有缓存项的访问位都设置为 0。
  • 创建一个环形指针数组,数组中的每个槽位对应一个缓存项,并按照某种顺序排列。
  • 当需要淘汰一个缓存项时,根据指针指向的槽位判断:
    • 如果该槽位的访问位为 0,表示该缓存项最近未被使用,可以选择淘汰。
    • 如果该槽位的访问位为 1,表示该缓存项最近被使用过,将访问位置为 0,并将指针向后移动一位。
  • 重复第 4 步,直到找到一个访问位为 0 的槽位,将该缓存项置换出来,让出空间给新的缓存项。
  • 如果需要访问某个缓存项时,将其对应的访问位置为 1,表示该缓存项已被使用。

(2)Clock 缓存淘汰算法相对于经典的最近未使用 (LRU) 算法具有更低的时间和空间复杂度。它通过近似地追踪缓存项的访问状态来进行淘汰决策,适用于中小规模的缓存系统。

(3)然而,需要注意的是,Clock 算法可能出现缓存项的“反复使用”情况,即缓存项被不断地替换出去又被重新引入,这可能会影响缓存的命中率。因此,在实际应用中,需要根据具体场景和需求,综合考虑各个因素,选择合适的缓存淘汰策略。其具体代码实现如下:

class ClockCache<K, V> {

    //循环链表节点
    static class CircleListNode<K, V> {
        K key;
        V value;
        boolean accessFlag;
        CircleListNode<K, V> pre;
        CircleListNode<K, V> next;

        public CircleListNode() {
        }

        public CircleListNode(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    private int capacity;
    //头节点
    private CircleListNode<K, V> dummyHead;
    private Map<K, CircleListNode<K, V>> cache;

    public ClockCache(int capacity) {
        this.capacity = capacity;
        this.dummyHead = new CircleListNode<>();
        this.dummyHead.next = this.dummyHead;
        this.dummyHead.pre = this.dummyHead;
        this.cache = new HashMap<>();
    }

    public V get(K key) {
        if (cache.containsKey(key)) {
            CircleListNode<K, V> node = cache.get(key);
            //将访问位设置为 true
            node.accessFlag = true;
            return node.value;
        } else {
            return null;
        }
    }

    public void put(K key, V value) {
        if (cache.containsKey(key)) {
            CircleListNode<K, V> node = cache.get(key);
            //将访问位设置为 true
            node.accessFlag = true;
            node.value = value;
        } else {
            if (cache.size() >= capacity) {
                //从最老的元素开始,此处直接从 head.next 开始,后续可以考虑优化记录这个 key
                CircleListNode<K, V> node = this.dummyHead;
                boolean removeFlag = false;
                while (node.next != this.dummyHead) {
                    //下一个元素
                    node = node.next;
                    if (!node.accessFlag) {
                        //未访问,直接淘汰
                        removeNode(node);
                        System.out.println(node.key);
                        removeFlag = true;
                        break;
                    } else {
                        //设置当前 accessFlag 为 false,继续遍历下一个
                        node.accessFlag = false;
                    }
                }
                if (!removeFlag) {
                    //如果循环一遍都没找到,直接取第一个元素即可
                    CircleListNode<K, V> firstNode = this.dummyHead.next;
                    System.out.println(firstNode.key);
                    removeNode(firstNode);
                }
            }
            CircleListNode<K, V> newNode = new CircleListNode<>(key, value);
            newNode.accessFlag = true;
            CircleListNode<K, V> tail = dummyHead.pre;
            tail.next = newNode;
            newNode.pre = tail;
            newNode.next = dummyHead;
            dummyHead.pre = newNode;

            cache.put(key, newNode);
        }
    }

    public void remove(K key) {
        CircleListNode<K, V> node = cache.get(key);
        if (node != null) {
            cache.remove(key);
            removeNode(node);
        }
    }

    public void clear() {
        cache.clear();
    }

    public int size() {
        return cache.size();
    }

	private void removeNode(CircleListNode<K, V> node) {
        CircleListNode<K, V> pre = node.pre;
        CircleListNode<K, V> next = node.next;

        pre.next = next;
        next.pre = pre;
        cache.remove(node.key);
    }
}

2.5.Random

(1)Random(随机)内存淘汰算法的思想是基于随机选择的策略来进行缓存淘汰。该算法不依赖于缓存项的访问频率或时间等信息,而是通过随机选择一个缓存项进行淘汰,没有明确的优先级或规则。Random 内存淘汰算法的思想如下:

  • 当缓存空间不足时,需要淘汰一个缓存项。
  • 使用随机数生成器(如 Random 类)来生成一个随机索引,范围为缓存的容量。
  • 根据生成的随机索引,随机选择一个缓存项进行淘汰。
  • 被选择的缓存项被移除,让出空间给新的缓存项。

(2)随机选择的特点使得每个缓存项被淘汰的概率相等,没有明确的优先级,所有缓存项都有被淘汰的可能性。这种随机性的特点适用于一些无规律或无明确访问模式的缓存使用场景。然而,随机内存淘汰算法可能导致缓存命中率下降,因为被频繁访问的缓存项有可能被随机选中被淘汰,从而增加缓存不命中的概率。

因此,在选择淘汰算法时,需要根据具体应用场景和缓存使用模式来权衡各种算法的优劣,并选择适合的淘汰策略以达到最优的性能。

(3)其具体代码实现如下:

class RandomCache<K, V> {
    private int capacity;
    private List<K> keys;
    private Map<K, V> cache;
    private Random random;

    public RandomCache(int capacity) {
        this.capacity = capacity;
        this.keys = new ArrayList<>(capacity);
        this.cache = new HashMap<>(capacity);
        this.random = new Random();
    }

    //接收一个键 key 并返回相应的值,如果键不存在,则返回 null
    public V get(K key) {
        return cache.getOrDefault(key, null);
    }

    public void put(K key, V value) {
        if (!cache.containsKey(key)) {
            //如果缓存已满,将使用 Random 对象的 nextInt 方法随机选择一个键索引并从列表中移除键
            if (keys.size() == capacity) {
                int index = random.nextInt(capacity);
                K randomKey = keys.remove(index);
                cache.remove(randomKey);
            }
            keys.add(key);
        }
        cache.put(key, value);
    }

	public V remove(K key) {
	    if (cache.containsKey(key)) {
	    	//从列表中移除指定键
	        keys.remove(key); 
	        //从缓存中移除指定键并返回对应的值
	        return cache.remove(key);
	    }
	    return null;
	}

	public void clear() {
	    keys.clear(); 
	    cache.clear(); 
	}
	
	public int size() {
		return cache.size();
	}
}

3.应用

Redis 的缓存淘汰策略如下:

在这里插入图片描述
有关上面淘汰策略的一些具体说明如下:

  • noevction 是 Redis 的默认配置。当缓存被写满时,再有写请求进来,Redis 不再提供服务,直接返回错误。
  • LRULFU 算法是常见的淘汰算法,其具体细节可以参考 146.LRU 缓存、460.LFU 缓存这两篇文章。
  • random 指随机删除,相关的算法实现可以参考 380. O(1) 时间插入、删除和获取随机元素这篇文章。
  • volatile-ttl 策略:针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的数据越先被淘汰,即 ttl 越小的数据越优先被淘汰,这里的 ttl 指 Time to Live,即生存时间。

要想设置 Redis 的缓存淘汰策略,可以在其配置文件 redis.conf 中进行 maxmemory-policy 具体淘汰策略 的设置,例如设置淘汰策略为 volatile-lru

maxmemory-policy volatile-lru

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/182927.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

最小二乘线性回归

​ 线性回归&#xff08;linear regression&#xff09;&#xff1a;试图学得一个线性模型以尽可能准确地预测实际值的输出。 以一个例子来说明线性回归&#xff0c;假设银行贷款会根据 年龄 和 工资 来评估可放款的额度。即&#xff1a; ​ 数据&#xff1a;工资和年龄&…

CSS特效017:球体涨水的效果

CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS…

app小程序定制的重点|软件定制开发|网站搭建

app小程序定制的重点|软件定制开发|网站搭建 App小程序定制开发是近年来快速发展的一项技术服务&#xff0c;随着移动互联网的普及和用户需求的不断升级&#xff0c;越来越多的企业和个人开始关注和需求定制化的小程序开发。那么&#xff0c;对于app小程序定制开发来说&#xf…

React中如何解决点击<Tree>节点前面三角区域不触发onClick事件

React中如何解决点击节点前面三角区域不触发onClick事件&#xff0c;如何区别‘左边’和‘右边’区域点击逻辑呢&#xff1f;&#xff08;Tree引用开源组件TDesign&#xff09; 只需要在onClick里面加限制一下就行&#xff1a; <TreeexpandMutexactivabletransitiondata{t…

使用XHProf查找PHP性能瓶颈

使用XHProf查找PHP性能瓶颈 XHProf是facebook 开发的一个测试php性能的扩展&#xff0c;本文记录了在PHP应用中使用XHProf对PHP进行性能优化&#xff0c;查找性能瓶颈的方法。 下载 网上很多是编译安装xhprof-0.9.4版本&#xff0c;应该是用php5&#xff0c;在php8.0下编译x…

C++语法知识点-vector+子数组

C语法知识点-vector子数组 一维数组定义无参数有参数迭代器扩容操作reserve 二维数组 vector 定义创建m*n的二维vectorvector< vector<int> > v(m, vector<int>(n) ) 初始化定义vector常用函数的实例分析访问操作resize 函数push _back ( )pop_back()函数siz…

【数据结构/C++】线性表_顺序表的基本操作

#include <iostream> using namespace std; #define MaxSize 10 // 1. 顺序表 // 静态分配 typedef struct {int data[MaxSize];int length; // 当前长度 } SqList; // 静态分配初始化顺序表 void InitList(SqList &L) {for (int i 0; i < MaxSize; i){L.data[i]…

基于yolov2深度学习网络的喝水行为检测系统matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、YOLOv2网络原理 4.2、基于YOLOv2的喝水行为检测 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 clc; clear; close all; warning off;…

Django之中间件与CSRF_TOKEN

文章目录 一、什么是中间件二、中间件有什么用三、Django自定义中间件中间件中主要方法及作用创建自定义中间件的步骤&#xff1a;process_request与process_response方法process_view方法process_exceptionprocess_template_response&#xff08;不常用&#xff09; 四、CSRF_…

python之pyqt专栏1-环境搭建

#python pyqt# python&#xff1a;3.11.6 pycharm&#xff1a;PyCharm Community Edition 2023.2.5 pyqt6 python安装 官网下载&#xff1a;Python Releases for Windows | Python.org pycharm社区版安装 官网地址&#xff1a;Download PyCharm: Python IDE for Professional…

Redis的性能,哨兵模式,集群,

Redis的性能管理; redis的数据保存在内存中 redis-cli info memory redis内存使用info memory命令参数解析 used_memory:236026888 由 Redis 分配器分配的内存总量&#xff0c;包含了redis进程内部的开销和数据占用的内存&#xff0c;以字节&#xff08;byte&#xff09…

安卓现代化开发系列——从生命周期到Lifecycle

由于安卓已经诞生快二十载&#xff0c;其最初的开发思想与现代的开发思想已经大相径庭&#xff0c;特别是Jetpack库诞生之后&#xff0c;项目中存在着新老思想混杂的情况&#xff0c;让许多的新手老手都措手不及&#xff0c;项目大步向屎山迈进。为了解决这个问题&#xff0c;开…

【双指针】有效三角形的个数

有效三角形的个数 611. 有效三角形的个数 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定一个包含非负整数的数组 nums &#xff0c;返回其中可以组成三角形三条边的三元组个数。 示例 1: 输入: nums [2,2,3,4] 输出: 3 解释:有效的组合是: 2,3,4 (使用第一个 2…

upload-labs关卡13(基于白名单的0x00截断绕过)通关思路

文章目录 前言一、回顾上一关知识点二、靶场第十三关通关思路1、看源代码2、bp进行0x00截断绕过3、蚁剑连接 总结 前言 此文章只用于学习和反思巩固文件上传漏洞知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随意去尚未授权的网站做渗透测试…

设计模式——行为型模式(二)

6.8 迭代器模式 6.8.1 概述 定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。 6.8.2 结构 迭代器模式主要包含以下角色: 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。具体聚合(ConcreteAggreg…

Java项目如何打包成Jar(最简单)

最简单的办法&#xff0c;使用Maven插件&#xff08;idea自带&#xff09; 1.选择需要打包的mudule&#xff0c;点击idea右侧的maven插件 2.clean操作 3.选择需要的其他mudule&#xff0c;进行install操作&#xff08;如果有&#xff09; 4.再次选择需要打包的module&#…

Spring Beans;Spring Bean的生命周期;spring Bean的作用域,spring处理线程并发问题

文章目录 Spring Beans请解释Spring Bean的生命周期解释Spring支持的几种bean的作用域Spring容器中的bean可以分为5个范围&#xff1a; Spring如何处理线程并发问题&#xff1f; 在现在的项目开发中经常使用到spring bean&#xff0c;那么来谈谈spring bean的生命周期&#xff…

基于DCT变换的图像压缩解压缩算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、DCT变换原理 4.2、基于DCT的图像压缩 4.3、基于DCT的图像解压缩 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ...................…

51单片机应用从零开始(七)·循环语句(if语句,swtich语句)

51单片机应用从零开始&#xff08;一&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;二&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;三&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;四&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;…

IIC驱动OLED HAL库+CubeMX

一.IIC传输数据的格式 1.写操作 2.读操作 3.IIC信号 二. IIC底层驱动 #define SCL_PIN GPIO_PIN_6 #define SDA_PIN GPIO_PIN_7#define SCL_PORT GPIOB #define SDA_PORT GPIOB/********************** 函数宏定义 **********************/ #d…