简单的LRU本地缓存实现-Java版本

在这里插入图片描述

文章目录

  • 什么是缓存
    • 缓存的种类
    • 缓存的关键特性
    • 缓存的优势与挑战
      • 优势:
      • 挑战:
    • 缓存的应用场景
    • 什么是LRUCache
    • LRU 缓存的工作原理
    • 核心操作
    • 为何选择 LRU
    • 使用场景
  • 一个简单的LRU缓存实现
  • 相关资料
    • 基础资料

什么是缓存

缓存(Cache)是一种高速数据存储层,它可以存储临时数据副本,让未来的请求能够更快地访问这些数据。缓存存在的主要目的是提高数据访问速度和提升系统的整体性能。缓存中的数据通常来源于原始数据的一个子集,这些原始数据可能存储在一个较慢的存储系统中,比如硬盘驱动器或远程数据库。

缓存的种类

  1. 硬件与软件缓存:
  • 硬件缓存:CPU缓存是一种高速存储器,能够暂存从主存储器(RAM)中读取的数据,从而加快数据访问速度。
  • 软件缓存:在软件开发中,可以实现多种类型的缓存策略来存储经常访问的数据或结果,如Web页面缓存、数据库缓存等。
  1. 客户端缓存和服务器端缓存:
  • 客户端缓存,如Web浏览器缓存,存储静态页面,以减少数据的重复下载。
  • 服务器端缓存,如数据库查询缓存,存储常见查询结果或计算结果,以减少数据库访问次数和计算负担。
  1. 分布式缓存:
    分布式缓存系统,如Redis和Memcached,提供了一个由多台服务器组成的缓存池,可以大幅提高缓存容量和处理能力。

缓存的关键特性

  1. 数据一致性:缓存数据与源数据在更新时需要保持一致,否则可能会出现数据过时的问题。
  2. 缓存淘汰策略:当缓存容量不足时,需要通过一定的策略删除部分缓存数据,常见策略有最近最少使用(LRU)、最不经常使用(LFU)等。

缓存的优势与挑战

优势:

减少了对原始数据源(如数据库)的访问次数,降低了系统的整体负载。
减少了数据访问的延迟,提供了更快的响应时间。
提高了资源的利用效率,可以通过缓存复用之前的计算结果。

挑战:

缓存与数据源之间的一致性管理。
缓存数据的过期策略和淘汰策略。
分布式缓存中的数据分片和同步问题。

缓存的应用场景

  • Web应用:缓存静态页面、图片、JavaScript文件等,减少服务器的计算和带宽消耗。
  • 数据库系统:缓存频繁执行的查询结果,减少数据库的查询次数。
  • 大数据处理:缓存中间结果,加快大数据计算和分析过程。

总的来说,缓存是提升系统性能、加快数据访问速度的关键技术之一。合理地设计和使用缓存可以极大提升应用程序的性能和用户体验。然而,也需要小心处理缓存带来的数据一致性和管理挑战。

什么是LRUCache

最近最少使用(LRU)缓存是一种常见的缓存策略,用以管理在有限的缓存空间中存储的数据。LRU 缓存的核心思想是当缓存达到最大容量时,优先移除最长时间未被访问的数据条目,为新的数据条目腾出空间。这种策略基于“局部性原理”,即最近被访问过的数据项未来被再次访问的概率较高。

LRU 缓存的工作原理

LRU 缓存使用一种数据结构来同时维护数据条目的插入顺序和访问顺序。这通常通过结合使用哈希表和双向链表来实现:

哈希表用于快速查找和访问缓存中的数据条目,达到近乎 O(1) 的访问时间复杂度。
双向链表用于维护所有数据条目的访问顺序。链表的头部代表了最近被访问的数据,而链表的尾部代表了最久未被访问的数据。

核心操作

  1. 访问数据(get):
    当访问一个数据项时,如果该项在缓存中,则需要将其移动到双向链表的头部,表示该数据项是最近被访问过的。
    如果数据项不在缓存中,则返回null或者相应的指示。
  2. 添加/更新数据(put):
    当插入一个新的数据项时,该数据项被放置在双向链表的头部,表示最近被访问过。同时,该数据项的键和寻址信息被存储在哈希表中。
    如果缓存达到最大容量,则需要从双向链表的尾部移除一个数据项,并且在哈希表中删除相应的项。
    如果插入的是一个现存的数据项(更新操作),则将该数据项移动到双向链表的头部,并更新哈希表中的值。

为何选择 LRU

LRU 缓存策略的优势在于其简洁而有效的机制,能够在保持缓存空间高效使用的同时,确保了频繁访问的数据可以快速被检索。这对于提高应用性能、降低数据访问延迟具有显著效果。

使用场景

LRU 缓存在各种场景中都有应用,特别是在需要快速访问数据的场合,如:

  • 数据库查询缓存
  • Web 页面缓存
  • 文件系统的缓存
  • 内容分发网络(CDN)

总之,LRU 缓存是一种广泛使用的缓存淘汰策略,通过优先移除最长时间未被访问的数据项,它能有效管理有限的缓存空间,并提高数据访问效率。

一个简单的LRU缓存实现


import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * A simple LRU cache implementation based on LinkedHashMap.
 * @param <K>
 * @param <V>
 */
public class LRUCache<K, V> {
    private final LinkedHashMap<K, CacheEntry<V>> cache;
    private final int maxSize;
    private final long maxLifetimeMillis;

    /**
     * 构造器 LRUCache(final int maxSize, final long maxLifetime, final TimeUnit maxLifetimeUnit):
     *
     * 创建一个 LRUCache 实例,并设置缓存的最大容量 maxSize 和元素的最大生存时间(maxLifetimeMillis,由 maxLifetime 和 maxLifetimeUnit 转换而来)。
     * 如果 maxSize 或 maxLifetime 是非正数,则会抛出 IllegalArgumentException 异常。
     * 缓存中元素的添加是基于 LinkedHashMap 的 access-order(访问顺序),即最近访问的元素会被放到队尾。
     * @param maxSize
     * @param maxLifetime
     * @param maxLifetimeUnit
     */
    public LRUCache(final int maxSize, final long maxLifetime, final TimeUnit maxLifetimeUnit) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize must be positive");
        }
        if (maxLifetime <= 0) {
            throw new IllegalArgumentException("maxLifetime must be positive");
        }
        this.maxSize = maxSize;
        this.maxLifetimeMillis = maxLifetimeUnit.toMillis(maxLifetime);
        this.cache = new LinkedHashMap<K, CacheEntry<V>>(maxSize, 0.75f, true) {
            protected boolean removeEldestEntry(Map.Entry<K, CacheEntry<V>> eldest) {
                long now = System.currentTimeMillis();
                V value = eldest.getValue().value;
                long creationTime = eldest.getValue().creationTime;
                // Remove the oldest item if either the cache size is exceeded
                // or if the oldest item's lifetime is expired
                return (size() > LRUCache.this.maxSize)
                        || ((now - creationTime) > LRUCache.this.maxLifetimeMillis);
            }
        };
    }

    /**
     * 将键值对存入缓存中,如果键或值为空则抛出 NullPointerException。
     * 如果缓存已满或某个元素过期,这个方法还会触发缓存中最旧的元素被清除(通过 LinkedHashMap 内部的 removeEldestEntry 方法定义)。
     * @param key
     * @param value
     */
    public void put(K key, V value) {
        long now = System.currentTimeMillis();
        if (key == null || value == null) {
            throw new NullPointerException("key and value must not be null");
        }
        synchronized (cache) {
            cache.put(key, new CacheEntry<>(value, now));
        }
    }

    /**
     * 从缓存中获取键对应的值,如果不存在则存入默认值 defaultValue 并返回。
     * @param key
     * @param defaultValue
     * @return
     */
    public V get(K key, V defaultValue) {
        synchronized (cache) {
            V value = get(key);
            if (value == null) {
                put(key, defaultValue);
                return defaultValue;
            }
            return value;
        }
    }

    /**
     * 从缓存中移除指定键及其对应的值。
     * @param key
     */
    public void invalidate(K key) {
        synchronized (cache) {
            cache.remove(key);
        }
    }

    /**
     * 移除并返回缓存中指定键对应的值。
     * @param key
     * @return
     */
    public V remove(K key) {
        synchronized (cache) {
            CacheEntry<V> entry = cache.remove(key);
            return entry == null ? null : entry.value;
        }
    }

    /**
     * 根据给定的键获取对应的值,并根据元素的生存时间判断是否过期;如果没有过期,则返回值并更新元素在缓存中的位置(访问顺序),否则从缓存中删除该元素并返回 null。
     * @param key
     * @return
     */
    public V get(K key) {
        synchronized (cache) {
            long now = System.currentTimeMillis();
            CacheEntry<V> entry = cache.get(key);
            if (entry != null && (now - entry.creationTime) <= maxLifetimeMillis) {
                // LinkedHashMap access-order policy will move the entry to the end
                return entry.value;
            } else {
                cache.remove(key);
                return null;
            }
        }
    }

    /**
     * 分别返回缓存中所有值的集合。
     * @return
     */
    public Set<V> values() {
        synchronized (cache) {
            return cache.values().stream().map(e -> e.value).collect(Collectors.toSet());
        }
    }

    /**
     * 分别返回缓存中所有键的集合。
     * @return
     */
    public Set<K> keys() {
        synchronized (cache) {
            return cache.keySet();
        }
    }

    /**
     * 判断缓存中是否包含指定键的元素,同时检查该元素是否过期。
     * @param key
     * @return
     */
    public boolean containsKey(K key) {
        synchronized (cache) {
            return cache.containsKey(key) && (System.currentTimeMillis() - cache.get(key).creationTime) <= maxLifetimeMillis;
        }
    }

    /**
     * 清空缓存中的所有元素。
     */
    public void clear() {
        synchronized (cache) {
            cache.clear();
        }
    }

    /**
     * 获取缓存当前的大小。
     * @return
     */
    public int size() {
        synchronized (cache) {
            return cache.size();
        }
    }

    /**
     * 获取缓存当前的最大容量。
     * @return
     */
    public int getMaxSize() {
        return maxSize;
    }

    private static class CacheEntry<V> {
        final V value;
        final long creationTime;

        CacheEntry(V value, long creationTime) {
            this.value = value;
            this.creationTime = creationTime;
        }
    }
}

相关资料

缓存技术在计算机科学和软件工程中扮演着至关重要的角色,特别是在提高应用性能、降低数据访问延迟和减轻后端系统负载方面。以下是关于缓存的一些关键资料和资源,包括定义、类型、策略、工具和最佳实践。

基础资料

  1. 缓存定义和原理:
  • “计算机组织与设计 - 硬件/软件接口”(David A. Patterson 和 John L. Hennessy),这本书详细讲述了计算机体系结构中缓存的基础概念和设计。
  • MDN Web Docs中的“HTTP 缓存”,提供基于Web的缓存机制、策略和控制方法的深入解析(网址:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Caching )。
    缓存类型:
  1. 客户端缓存,例如浏览器缓存。
  • 服务器端缓存,如Web缓存、数据库缓存。
  • 分布式缓存系统,例如Redis、Memcached。
  1. 实现和工具
  • 分布式缓存系统:
    Redis:一个开源的使用内存存储数据的数据结构服务器,可以用作数据库、缓存和消息中间件(网址:https://redis.io/ )。

  • Memcached:一个高性能的分布式内存对象缓存系统,旨在加速动态Web应用程序通过减轻数据库负载(网址:http://memcached.org/ )。
    本地缓存库:

  • Guava Cache:谷歌的Java编程库Guava提供了一个强大的内存缓存机制,支持多种缓存过期策略和缓存淘汰策略(网址:https://github.com/google/guava/wiki/CachesExplained )。

  • Caffeine:一个Java 8的高性能缓存库,被认为是Guava Cache的后继者(网址:https://github.com/ben-manes/caffeine )。

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

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

相关文章

机器人课程教师面对的困境有哪些(补充)

唯有自救&#xff0c;唯有自强&#xff0c;方能有希望。 前序 距离这一篇博文发表已经快2年了…… 机器人课程教师面对的困境有哪些 至少从5年前就已经有需求减少&#xff0c;供给过剩的现象出现了。 为何在2019年之后应用型本科开设ROS课程优势消逝 案例 博客分享过工作…

VSCode 目录折叠展开、缩进深度设置

1、VSCode 目录折叠展开设置 运行 Visual Studio Code &#xff0c;按 Ctrl &#xff0c;打开设置 输入Explorer:Compact Folders&#xff0c;取消勾选 或者在设置文件上添加 "explorer.compactFolders": false2、VSCode 目录缩进深度设置 输入Workbench Tree:…

AI大模型日报#0420:开源模型击败GPT-4、西湖大学蛋白质通用大模型、GPT的七条经验

导读&#xff1a; 欢迎阅读《AI大模型日报》&#xff0c;内容基于Python爬虫和LLM自动生成。目前采用“文心一言”生成了每条资讯的摘要。 标题: 开源模型打败GPT-4&#xff01;LLM竞技场最新战报&#xff0c;Cohere Command R上线 摘要: GPT-4在LLM竞技场被开源模型Cohere的…

【开发问题记录】启动某个服务时请求失败(docker-componse创建容器时IP参数不正确)

问题记录 一、问题描述1.1 产生原因1.2 产生问题 二、问题解决2.1 找到自己的docker-compose.yml文件2.2 重新编辑docker-compose.yml文件2.3 通过docker-componse重新运行docker-compose.yml文件2.4 重新启动docker容器2.5 查看seata信息 一、问题描述 1.1 产生原因 因为我是…

在ubuntu20.04下迁移anaconda的目录,试验不行后,换成软连接

一、原因 随着不断的搭建不同的算法环境&#xff0c;原本在固态硬盘上安装的anaconda上占用空间越来越多。导致可用的固态硬盘空间越来越少&#xff0c;又因安装的环境太多&#xff0c;重新搭建比较费时费力。有没有直接将当前已经搭建好环境的anaconda 迁移到另外的目录呢&…

算法题解记录19+++回文链表(百日筑基)

题目描述&#xff1a; 难度&#xff1a;简单 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示…

Kotlin语法快速入门--变量声明(1)

Kotlin语法入门–变量声明&#xff08;1&#xff09; 文章目录 Kotlin语法入门--变量声明&#xff08;1&#xff09;一、变量声明1、整型2、字符型3、集合3.1、创建array数组3.2、创建list集合3.3、不可变类型数组3.4、Set集合--不重复添加元素3.5、键值对集合Map 4、kotlin特有…

【Python系列】python 如何打印带时间的日志

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【软件测试】正交表测试例题

【软件测试】正交表测试 例题1答案 例题2答案 例题3答案 例题1 很多Word编辑器都有字体修饰功能&#xff0c;可以将一个字加粗、倾斜、以及加上下划线。一个字可以同时被加粗和倾斜&#xff0c;也可以同时被倾斜和加下划线。三种因子Bold, Italic, Underline的效果可以任意组合…

累加(C语言)

一、题目&#xff1b; 二、N-S流程图&#xff1b; 三、运行结果&#xff1b; 四、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>int main() {//初始化变量值&#xff1b;int i 0;int j 0;int n 5;int result 0;int sum 0;//运算&#…

【氧化镓】Ga2O3 MOSFET器件的单SEB机制TCAD研究

本文是一篇关于氧化镓(Ga2O3)金属氧化物半导体场效应晶体管(MOSFET)在单粒子烧毁(single event burnout, SEB)事件中的机制研究的文章。文章通过使用技术计算机辅助设计(TCAD)模拟来探究侧向耗尽型氧化镓MOSFET设备在SEB中的敏感区域和安全操作电压&#xff0c;并提出了辐射损伤…

俊杰测评:电视盒子什么牌子好?电视盒子品牌排行榜

欢迎各位来到俊杰的数码测评频道&#xff0c;每年我会进行数十次电视盒子测评&#xff0c;今年已经买过二十多款电视盒子了&#xff0c;本期的测评主题是电视盒子什么牌子好&#xff0c;通过十天的深入详细对比后我整理了电视盒子品牌排行榜&#xff0c;近期想买电视盒子的可以…

C++运算符

运算符 作用&#xff1a;用于执行代码的运算 本文章主要讲解以下四种运算符&#xff1a; 1.算术运算符 作用&#xff1a;用于处理四则运算 算术运算符包括以下这些符号&#xff1a; 举例&#xff1a; 注&#xff1a; 在除法运算中&#xff0c;除数不能为0 在取模运算…

【MATLAB源码-第194期】基于matlab的MB-OFDM仿真,超宽带(UWB)无线传输。对比LS/DFT及其改进算法。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 一、无线通信的基本原理 无线通信是通过空气或其他介质传播电磁波来传输信息的技术。这种通信方式的核心在于电磁波&#xff0c;它能够在没有物理连接的情况下传输数据。无线通信的基本流程包括&#xff1a; 信号的生成&am…

Redis集合[持续更新]

Redis&#xff08;全称&#xff1a;Remote Dictionary Server 远程字典服务&#xff09;是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库&#xff0c;并提供多种语言的 API。 数据结构 1. string 字符串 字符串类型是 Redis 最…

springboot实现SSE之牛刀小试

文章目录 一&#xff0c;概述1.SSE是何方神圣&#xff1f;2.sse与webscoket区别 二&#xff0c;实现过程1.效果展示2. 简要流程3. 源码放送4.完整项目 一&#xff0c;概述 1.SSE是何方神圣&#xff1f; SSE 全称Server Sent Event&#xff0c;直译一下就是服务器发送事件。 …

FPGA中闪灯程序设计示例

在FPGA设计中&#xff0c;闪灯的作用主要是用于测试和验证设计的功能和性能。具体来说&#xff0c;闪灯可以作为一个可视化的指示器&#xff0c;通过控制LED灯的闪烁模式和频率&#xff0c;来显示FPGA的工作状态或调试信息。 例如&#xff0c;在设计过程中&#xff0c;可以编写…

channel_shuffle代码实现

结构图&#xff0c;先将输入的图像进行通道拆分为组GConv1&#xff0c;每个GConv1再拆分Feature&#xff0c;每个GConv1的Feature进行合并GConv2&#xff0c;输出Output 输入图像x&#xff0c;拆分为groups个组&#xff0c;每隔组的通道数为channels_per_group batch_size, n…

binary tree Leetcode 二叉树算法题

144.二叉树的前序遍历 前序遍历是&#xff1a;根-左-右 所以记录序列的的时候放在最前面 递归 class Solution {List<Integer> ans new ArrayList<>();public List<Integer> preorderTraversal(TreeNode root) {if(root null) return ans;ans.add(root…

请编写函数fun,它的功能是:求出1到1000之内能被7或11整除、但不能同时被7和11整除的所有整数并将它们放在a所指的数组中,通过n返回这些数的个数。

本文收录于专栏:算法之翼 https://blog.csdn.net/weixin_52908342/category_10943144.html 订阅后本专栏全部文章可见。 本文含有题目的题干、解题思路、解题思路、解题代码、代码解析。本文分别包含C语言、C++、Java、Python四种语言的解法和详细的解析。 题干 请编写函数fu…