【本地缓存篇】LFU、LRU 等缓存失效算法

在这里插入图片描述

LFU、LRU 等缓存失效算法

  • ✔️ 解析
  • ✔️FIFO
  • ✔️LRU
  • ✔️LFU
  • ✔️W-TinyLFU

✔️ 解析


缓存失效算法主要是进行缓存失效的,当缓存中的存储的对象过多时,需要通过一定的算法选择出需要被淘汰的对象,一个好的算法对缓存的命中率影响是巨大的。常见的缓存失效算法有FIFO、LRU、LFU,以及Caffeine中的Window TinyLFU算法。


✔️FIFO


FIFO 算法是一种比较容易实现也最容易理解的算法。它的主要思想就是和队列是一样的,即先进先出(First in First Out)。


一般认为一个数据是最先进入的,那么可以认为在将来它被访问的可能性很小。


因为FIFO刚好符合队列的特性,所以通常FIFO的算法都是使用队列来实现的:


在这里插入图片描述

1 . 新数据插入到队列尾部,数据在队列中顺序移动


2 . 淘汰队列头部的数据


Code.1:




/**
*    FIFO算法实现Demo,使用一个自定义的Node类来表示队列中的元素
*/
class Node {  
    int data;  
    Node next;  
  
    Node(int data) {  
        this.data = data;  
        this.next = null;  
    }  
}  
  
public class ComplexFIFOQueue {  
    private Node head;  
    private Node tail;  
    private int size;  
  
    public ComplexFIFOQueue() {  
        head = null;  
        tail = null;  
        size = 0;  
    }  
  
    // 入队操作  
    public void enqueue(int item) {  
        Node newNode = new Node(item);  
        if (tail == null) {  
            head = newNode;  
            tail = newNode;  
        } else {  
            tail.next = newNode;  
            tail = newNode;  
        }  
        size++;  
    }  
  
    // 出队操作  
    public int dequeue() {  
        if (head == null) {  
            return -1; // 队列为空,返回一个特殊值表示错误  
        } else {  
            int item = head.data;  
            head = head.next;  
            size--;  
            return item;  
        }  
    }  
  
    // 查看队首元素  
    public int peek() {  
        if (head == null) {  
            return -1; // 队列为空,返回一个特殊值表示错误  
        } else {  
            return head.data;  
        }  
    }  
  
    // 检查队列是否为空  
    public boolean isEmpty() {  
        return head == null;  
    }  
  
    // 获取队列大小  
    public int size() {  
        return size;  
    }  
}

一个自定义的Node类来表示队列中的每个元素。每个节点都有一个数据字段和一个指向下一个节点的指针。我们还增加了一些其他方法,例如检查队列是否为空,获取队列大小等。这使得FIFO算法的实现更加完整和复杂。


✔️LRU


LRU (The Least Recently Used,最近最少使用)是一种常见的缓存算法,在很多分布式缓存系统(如RedisMemcached)中都有广泛使用。


LRU算法的思想是:如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小因此,当空间满时,最久没有访问的数据最先被淘汰。


最堂见的实现是使用一个链表保存缓存数据,详细算法实现如下:


在这里插入图片描述


1 . 新数据插入到链表头部


2 . 每当缓存命中,则将数据移到链表头部


3 . 当链表满时,将链表尾部的数据丢失

Code.2:


import java.util.HashMap;  
import java.util.Map;  
/**
*    LRU算法实现Demo,使用双向链表和哈希表来存储缓存数据,并支持动态调整缓存大小
*/  
public class ComplexLRUCache<K, V> {  
    private static final int DEFAULT_INITIAL_CAPACITY = 4;  
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;  
    private final int capacity;  
    private final Map<K, Node<K, V>> cacheMap;  
    private final Node<K, V> head;  
    private final Node<K, V> tail;  
  
    private static class Node<K, V> {  
        K key;  
        V value;  
        Node<K, V> prev;  
        Node<K, V> next;  
  
        Node(K key, V value) {  
            this.key = key;  
            this.value = value;  
        }  
    }  
  
    public ComplexLRUCache(int capacity) {  
        this.capacity = capacity;  
        this.cacheMap = new HashMap<>(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);  
        this.head = new Node<>(null, null);  
        this.tail = new Node<>(null, null);  
        head.next = tail;  
        tail.prev = head;  
    }  
  
    public synchronized V get(K key) {  
        Node<K, V> node = cacheMap.get(key);  
        if (node == null) {  
            return null;  
        }  
        moveToHead(node);  
        return node.value;  
    }  
  
    public synchronized void put(K key, V value) {  
        Node<K, V> node = cacheMap.get(key);  
        if (node == null) {  
            if (cacheMap.size() >= capacity) {  
                evict(); // 缓存已满,删除最老的数据(即尾部数据)  
            }  
            node = new Node<>(key, value); // 创建新节点并添加到头部  
            cacheMap.put(key, node); // 将新节点添加到哈希表中  
            addToHead(node); // 将新节点添加到双向链表头部(表示最近使用)  
        } else { // 更新节点值并移动到头部(表示最近使用)  
            node.value = value;  
            moveToHead(node);  
        }  
    }  
  
    private void addToHead(Node<K, V> node) {  
        node.prev = head;  
        node.next = head.next;  
        head.next.prev = node;  
        head.next = node;  
    }  
  
    private void moveToHead(Node<K, V> node) {  
        removeNode(node); // 先从链表中移除该节点(防止出现循环引用)  
        addToHead(node); // 再将节点添加到链表头部(表示最近使用)  
    }  
  
    private void removeNode(Node<K, V> node) {  
        node.prev.next = node.next; // 将当前节点从链表中移除(前驱节点的后继指向当前节点的后继)  
        node.next.prev = node.prev; // 将当前节点从链表中移除(后继节点的前驱指向当前节点的后继的前驱)  
    }  
  
    private void evict() { // 删除最老的数据(即尾部数据)并从哈希表中删除该节点引用(防止出现循环引用)  
        Node<K, V> tailNode = tail.prev; // 获取尾部节点的前驱节点(即最老的数据)  
        removeNode(tailNode); // 从链表中移除该节点(前驱节点的后继指向当前节点的后继)和从哈希表中删除该节点引用(防止出现循环引用)  
        cacheMap.remove(tailNode.key); // 从哈希表中删除该键的引用(防止出现循环引用)  
    }  
}

✔️LFU


LFU (Least Frequently Used ,最近最不常用)也是 种常见的缓存算法。


顾名思义,LFU算法的思想是:如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰。


LFU的每个数据块都有一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块则按照时间排
序。


具体实现如下:

<br/。

在这里插入图片描述

1 . 新加入数据插入到队列尾部 (因为引用计数为1)


2 . 队列中的数据被访问后,引用计数增加,队列重新排序


3 . 当需要淘汰数据时,将已经排序的列表最后的数据块删除


Code.3


import java.util.*;  

/**
*    LFUCache实现Demo,包括删除和更新操作
*/  
public class LFUCache<K, V> {  
    private static final int DEFAULT_INITIAL_CAPACITY = 4;  
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;  
    private final int capacity;  
    private final Map<K, Node<K, V>> cacheMap;  
    private final Map<K, Integer> accessCountMap;  
    private final Deque<Node<K, V>> deque;  
  
    private static class Node<K, V> {  
        K key;  
        V value;  
        int count;  
        Node<K, V> prev;  
        Node<K, V> next;  
  
        Node(K key, V value, int count) {  
            this.key = key;  
            this.value = value;  
            this.count = count;  
        }  
    }  
  
    public LFUCache(int capacity) {  
        this.capacity = capacity;  
        this.cacheMap = new HashMap<>(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);  
        this.accessCountMap = new HashMap<>();  
        this.deque = new LinkedList<>();  
    }  
  
    public synchronized V get(K key) {  
        Node<K, V> node = cacheMap.get(key);  
        if (node == null) {  
            return null;  
        }  
        updateAccessCount(node); // 更新访问次数  
        moveToHead(node); // 移动到头部(表示最近使用)  
        return node.value;  
    }  
  
    public synchronized void put(K key, V value) {  
        Node<K, V> node = cacheMap.get(key);  
        if (node == null) { // 新增节点(不存在该键)  
            if (cacheMap.size() >= capacity) { // 缓存已满,删除最少使用的数据(即尾部数据)  
                evict(); // 删除最少使用的数据(即尾部数据)并从哈希表中删除该节点引用(防止出现循环引用)和从访问次数映射中删除该键的引用(防止出现循环引用)  
            }  
            node = new Node<>(key, value, 1); // 创建新节点并设置访问次数为1,表示首次访问该键的数据(即新增数据)  
           
           /** 将新节点添加到哈希表中,并将该键与节点引用的映射关系保存到访问次数映射中,
           以便快速获取节点引用和更新节点引用指向链表中的位置(头部)和访问次数(最近使用)等信息。
           同时,将新节点添加到链表头部(表示最近使用)和从哈希表中删除该键的引用(防止出现循环引用)。
           */
              cacheMap.put(key, node);
        }         
}

✔️W-TinyLFU


LFU 通常能带来最佳的缓存命中率,但 LFU 有两个缺点:


1 . 它需要给每个记录项维护频率信息,每次访问都需要更新,需要一个巨大的空间记录所有出现过的 key 和其对应的频次;


2 ,如果数据访问模式随时间有变,LFU 的频率信息无法随之变化,因此早先频繁访问的记录可能会占据缓存而后期访问较多的记录则无法被命中;


3 . 如果一个刚加入缓存的元素,它的频率并不高,那么它可能会会直接被淘汰。


其中第一点过于致命导致我们通常不会使用 LFU。我们最常用的 LRU 实现简单,内存占用低,但其并不能反馈访问频率。LFU 通常需要较大的空间才能保证较好的缓存命中率。


W-TinyLFU是一种高效的缓存淘汰算法,它是TinyLFU算法的一种改进版本,主要用于处理大规模缓存系统中的淘汰问题。W-TinyLFU的核心思想是基于窗口的近似最少使用算法,即根据数据的访问模式动态地调整缓存中教据的淘汰策略。W-TinyLFU 综合了LRU和LFU的长处: 高命中率、低内存占用。


W-TinyLFU由多个部分组合而成,包括窗口缓存、过滤器和主缓存。


在这里插入图片描述

使用LRU来作为一个窗口缓存,主要是让元素能够有机会在窗口缓存中去积累它的频率,避免因为频率很低而直接被淘汰。


主缓存是使用SLRU,元素刚进入W-TinyLFU会在窗口缓存暂留一会,被挤出窗口缓存时,会在过滤器中和主缓存中最容易被淘汰的元素进行PK,如果频率大于主缓存中这个最容易被淘汰的元素,才能进入主缓存。


Code .4:


import java.util.*;  
  
public class WTLFUCache<K, V> {  
    private static final int DEFAULT_INITIAL_CAPACITY = 4;  
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;  
    private final int capacity;  
    private final Map<K, Node<K, V>> cacheMap;  
    private final Map<K, Integer> accessCountMap;  
    private final Deque<Node<K, V>> deque;  
    private final Map<K, Integer> weightMap;  
    private int totalWeight;  
  
    private static class Node<K, V> {  
        K key;  
        V value;  
        int count;  
        int weight;  
        Node<K, V> prev;  
        Node<K, V> next;  
  
        Node(K key, V value, int count, int weight) {  
            this.key = key;  
            this.value = value;  
            this.count = count;  
            this.weight = weight;  
        }  
    }  
  
    public WTLFUCache(int capacity) {  
        this.capacity = capacity;  
        this.cacheMap = new HashMap<>(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);  
        this.accessCountMap = new HashMap<>();  
        this.deque = new LinkedList<>();  
        this.weightMap = new HashMap<>();  
        this.totalWeight = 0;  
    }  
  
    public synchronized V get(K key) {  
        Node<K, V> node = cacheMap.get(key);  
        if (node == null) {  
            return null;  
        }  
        updateAccessCount(node); // 更新访问次数和权重计数器  
        moveToHead(node); // 移动到头部(表示最近使用)  
        return node.value;  
    }  
  
    public synchronized void put(K key, V value) {  
        Node<K, V> node = cacheMap.get(key);  
        if (node == null) { // 新增节点(不存在该键)  
            if (cacheMap.size() >= capacity) { // 缓存已满,删除最少使用的数据(即尾部数据)  
                evict(); // 删除最少使用的数据
            }
            //

        }
}

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

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

相关文章

排列组合算法(升级版)

前言 在上一期博客中我们分享了一般的排列组合算法&#xff08;没看的话点这里哦~&#xff09;&#xff0c;但是缺点很明显&#xff0c;没法进行取模运算&#xff0c;而且计算的范围十分有限&#xff0c;而今天分享的排列组合升级版算法能够轻松解决这些问题&#xff0c;话不多…

Matlab:非线性规划

1、语法&#xff1a; xfmincon(fun,x0,A,b) xfmincon(fun,x0,A,b,Aeq,beq) xfmincon(fun,x0,A,b,Aeq,beq,lb,ub) xfmincon(fun,x0,A,b,Aeq,beq,lb,ub,nonlcon) xfmincon(fun,x0,A,b,Aeq,beq,lb,ub,nonlcon,options) xfmincon(problem) [x,fval]fmincon(___) [x,fval,exitflag,…

Leetcode—2660.保龄球游戏的获胜者【简单】

2023每日刷题&#xff08;七十二&#xff09; Leetcode—2660.保龄球游戏的获胜者 实现代码 class Solution { public:int isWinner(vector<int>& player1, vector<int>& player2) {long long sum1 0, sum2 0;int n player1.size();for(int i 0; i &…

跟小德学C++之参数处理

嗨&#xff0c;大家好&#xff0c;我是出生在达纳苏斯的一名德鲁伊&#xff0c;我是要立志成为海贼王&#xff0c;啊不&#xff0c;是立志成为科学家的德鲁伊。最近&#xff0c;我发现我们所处的世界是一个虚拟的世界&#xff0c;并由此开始&#xff0c;我展开了对我们这个世界…

go 使用 - sync.Metux

[TOC]&#xff08;sync.metux 使用&#xff09; 简介 简述使用metux使用的方法&#xff0c; 使用的注意点&#xff0c; 以及使用情况使用方法 提供的方法 Lock() 方法用于获取锁 Unlock() 方法用于释放锁 TryLock()方法尝试获取锁 对共享资源进行加锁&#xff0c; 例 &#…

1.3MySQL中的自连接

自己的表和自己连接&#xff0c;核心&#xff1a;一张表拆为两张一样的表。 语法&#xff1a;select 字段列表 from 表 [as] 表别名1,表 [as] 表别名2 where 条件...; 关于怎样把一个表拆分成一个表&#xff0c;只要给它们分别取别名就行 categoryidpidcategoryname21信息…

errors包返回堆栈信息的性能测试

errors包返回堆栈信息的性能测试 上一篇Golang中使用errors返回调用堆栈信息 讲了使用第三方开源库的errors github.com/go-errors/errors&#xff0c;错误信息带调用栈&#xff0c;方便定位错误的抛出位置。 通过堆栈的信息来定位是方便了&#xff0c;性能怎么样&#xff0c…

力扣:452. 用最少数量的箭引爆气球(贪心)

题目&#xff1a; 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。…

英飞凌TC3xx之一起认识GTM系列(一)先来认识GTM架构

英飞凌TC3xx之一起认识GTM系列(一)先来认识GTM架构 1 先来认识GTM的通用架构2 概览2.1 架构的简要说明2.2 架构概述1 先来认识GTM的通用架构 GTM系统使用GTM全局时钟fGTM 运行(本文称为SYS_CLK)。 特点如下: GTM模块由两个主要部分组成: 由博世设计的GTM IP v3.1.5.1 …

gitee+picgo+typora图床搭建

giteepicgotypora图床搭建 1.安装typora 官网下载直接安装&#xff1a;https://www.typora.io/#download 2.编辑typora图像设置 打开 文件 -> 偏好设置 -> 图像设置 插入图片时 选择 上传图片设置 上传服务 为 PicGo-Core(command line) 3.为typora安装PicGo-Core 点…

IntelliJ IDEA Apache Dubbo,IDEA 官方插件正式发布!

作者&#xff1a;刘军 最受欢迎的 Java 集成开发环境 IntelliJ IDEA 与开源微服务框架 Apache Dubbo 社区强强合作&#xff0c;给广大微服务开发者带来了福音。与 IntelliJ IDEA 2023.2 版本一起&#xff0c;Jetbrains 官方发布了一款全新插件 - Apache Dubbo in Spring Frame…

BAQ压缩MATLAB仿真

本专栏目录: ​​​​​​​全球SAR卫星大盘点与回波数据处理专栏目录-CSDN博客 我们按照上一期文章的BAQ原理编写MATLAB代码,进行baq压缩与解压缩的全流程验证,并分析BAQ压缩对信号指标造成的影响。 生成3个点目标回波数据,加入高斯噪声,对回波进行BAQ压缩和解BAQ压缩,…

webstrom 快速创建typescript 语法检测的Vue3项目

webstrom 快速创建typescript 语法检测的Vue3项目 若您想为您的Vue 3项目添加TypeScript支持&#xff0c;您需要进行以下步骤&#xff1a; 安装 typescript 和 vitejs/plugin-vue 作为开发依赖项&#xff1a; npm install --save-dev typescript vitejs/plugin-vue创建一个…

uni-app uni.scss内置全局样式变量

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

论文润色的重要性是什么 papergpt

大家好&#xff0c;今天来聊聊论文润色的重要性是什么&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 标题&#xff1a;论文润色的重要性是什么――探究论文润色在学术研究…

多维时序 | MATLAB实现SSA-GRU麻雀算法优化门控循环单元多变量时间序列预测

多维时序 | MATLAB实现SSA-GRU麻雀算法优化门控循环单元多变量时间序列预测 目录 多维时序 | MATLAB实现SSA-GRU麻雀算法优化门控循环单元多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.MATLAB实现SSA-GRU麻雀算法优化门控循环单元多变量时间序列预…

大数据应用开发2-Scala语言各个环境配置

一、首先安装JDK1.8版本(简单过一下) 1.下载与安装 下载Java1.8 地址&#xff1a;Java Downloads | Oracle 中国 点击跳转&#xff08;下载需要登录甲骨文账号&#xff09; 下载完成运行 修改安装目录&#xff08;两个都要改&#xff09; 复制第一次修改的安装目录 2.配置环…

Python关键字之旅:一步步掌握Python的奥秘

文章目录 一、前言二、关键字1.总表&#xff08;共35个&#xff09;2.拆分2.1 False None True2.2 and not or2.3 as from import2.4 assert2.5 async await2.6 break continue2.7 class def2.8 del2.9 if elif else2.10 try except finally raise2.11 for in while2.12 global…

企业计算机服务器中了360后缀勒索病毒如何处理,勒索病毒应对步骤

网络技术的应用与发展&#xff0c;为企业的生产运营提供了有力保障&#xff0c;但也为网络安全威胁埋下隐患。近期&#xff0c;网络上的勒索病毒非常嚣张&#xff0c;严重影响了企业的生产运营。近日&#xff0c;云天数据恢复中心接到很多企业的求助&#xff0c;企业的计算机服…

进程互斥的硬件实现方法-第二十五天

目录 中断屏蔽方法 TestAndSet&#xff08;TS指令/TSL指令&#xff09; Swap指令&#xff08;XCHG指令&#xff09; 本节思维导图 中断屏蔽方法 概念&#xff1a;利用“开/关中断指令”实现&#xff08;与原语的实现思想相同&#xff0c;即在某进程开始访问临界区到结束访…
最新文章