首页 > 编程语言 >如何实现缓存与LRU算法以及惰性过期

如何实现缓存与LRU算法以及惰性过期

时间:2024-03-13 22:32:38浏览次数:28  
标签:缓存 String cache System API LRU 惰性 public

如何实现缓存与LRU算法以及惰性过期

实现缓存概述与LRU算法详解

  • 缓存的基本概念与作用

在计算机科学中,缓存是一种临时存储数据的技术,用于加速数据访问速度。通过将常用数据存储在高速缓存中,可以减少对慢速存储器(如磁盘或数据库)的访问次数,从而提高系统的性能和响应速度。缓存通常位于计算机内存或更快速的存储介质上。

  • 用户实现缓存的原理与好处

用户实现缓存是指开发人员根据应用程序的需求,手动实现缓存机制,以提高系统的性能和响应速度。相比于由系统自动管理的缓存机制,用户实现缓存可以更灵活地控制缓存的存储策略、过期策略和淘汰策略,从而更好地满足特定场景下的需求。

用户实现缓存的好处包括:

  • 提高系统性能:减少了对慢速存储介质的访问次数,加快了数据访问速度。
  • 降低资源消耗:通过在内存中存储数据,减少了对其他资源(如网络带宽、数据库连接)的消耗。
  • 改善用户体验:缩短了数据加载和响应时间,提高了用户体验质量。
  • LRU算法的原理与实现

LRU(Least Recently Used,最近最少使用)算法是一种常用的缓存淘汰策略,它的核心思想是基于数据的访问历史,将最近最少被使用的数据替换出缓存。LRU算法的实现通常基于双向链表和哈希表。

LRU算法的实现步骤包括

  • 使用双向链表(LinkedHashMap)来保存缓存中的键值对,链表头部表示最近访问过的数据,尾部表示最久未访问的数据。
  • 使用哈希表来保存每个键对应的链表节点,以实现快速查找和访问。
  • 当访问一个数据时,如果数据已经存在于缓存中,则将其移动到链表头部;如果数据不存在于缓存中,则将其添加到链表头部,并在需要时移除链表尾部的数据。

LRU算法的Java代码实现示例

import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(2);
        cache.put(1, "a");
        cache.put(2, "b");
        System.out.println(cache); // 输出 {1=a, 2=b}
        cache.get(1);
        System.out.println(cache); // 输出 {2=b, 1=a}
        cache.put(3, "c");
        System.out.println(cache); // 输出 {1=a, 3=c}
    }
}

用户实现缓存的惰性过期机制

  • 缓存过期的意义与作用

缓存过期是指缓存中的数据在一定时间内有效,超过该时间后将被自动清除或标记为无效。缓存过期机制的作用在于确保缓存中的数据始终保持最新和有效,避免因为缓存中的旧数据而引发的问题。

  • 惰性过期的概念与原理

惰性过期是一种缓存过期策略,它的原理是在缓存数据被访问时检查其是否已经过期,如果过期则在需要时再进行清除。相比于定期清理或定时清理的方式,惰性过期可以更好地节省资源,并且避免在不必要的情况下进行缓存清理。

  • Java代码实现惰性过期机制

为了实现惰性过期机制,可以使用定时器(Timer)或者线程池(ThreadPoolExecutor)来周期性地检查缓存中的数据是否过期,如果过期则进行清理。下面是一个简单的Java代码示例:

import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

public class LazyExpiryCache<K, V> {
    private final Map<K, V> cache = new ConcurrentHashMap<>();
    private final Map<K, Long> expiryTimes = new ConcurrentHashMap<>();
    private final Timer timer = new Timer();

    public void put(K key, V value, long expiryTimeMillis) {
        cache.put(key, value);
        expiryTimes.put(key, System.currentTimeMillis() + expiryTimeMillis);
    }

    public V get(K key) {
        V value = cache.get(key);
        if (value != null && System.currentTimeMillis() >= expiryTimes.getOrDefault(key, Long.MAX_VALUE)) {
            cache.remove(key);
            expiryTimes.remove(key);
            return null;
        }
        return value;
    }

    public static void main(String[] args) {
        LazyExpiryCache<String, Integer> cache = new LazyExpiryCache<>();
        cache.put("key1", 100, 5000); // 缓存5秒钟
        System.out.println(cache.get("key1")); // 输出 100
        try {
            Thread.sleep(6000); // 等待6秒钟,缓存过期
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(cache.get("key1")); // 输出 null,缓存已过期
    }
}

在这个示例中,使用了Timer来周期性地检查缓存中的数据是否过期,并在过期时进行清理。需要注意的是,使用Timer会有一定的性能开销,并且可能会受到系统时间的影响,因此在生产环境中可以考虑使用ScheduledExecutorService来替代。

案例分析:使用用户实现缓存解决实际问题

  • 场景一:网站页面缓存

在Web开发中,网站页面缓存是常见的优化手段之一。通过将网站的页面内容缓存到内存或其他高速存储介质中,可以减少数据库查询和页面渲染的时间,提高网站的响应速度和用户体验。

Java代码示例:网页内容缓存实现与页面访问优化

假设有一个简单的网站,包含多个页面,每个页面都需要从数据库中获取数据并动态生成。我们可以使用用户实现缓存来缓存每个页面的内容,在页面被访问时直接从缓存中获取,而不是每次都重新生成页面内容。

以下是一个简化的示例代码:

import java.util.HashMap;
import java.util.Map;

public class WebPageCache {
    private final Map<String, String> cache = new HashMap<>();

    public String getPageContent(String url) {
        String content = cache.get(url);
        if (content == null) {
            // 如果缓存中不存在该页面内容,则从数据库或其他数据源中获取,并放入缓存中
            content = generatePageContent(url);
            cache.put(url, content);
        }
        return content;
    }

    private String generatePageContent(String url) {
        // 从数据库或其他数据源中获取页面内容的逻辑
        // 这里为了简化示例,直接返回一个假的页面内容
        return "<html><head><title>" + url + "</title></head><body>Page content for " + url + "</body></html>";
    }

    public static void main(String[] args) {
        WebPageCache cache = new WebPageCache();
        String page1 = cache.getPageContent("/page1");
        System.out.println(page1); // 输出页面1的内容
        String page2 = cache.getPageContent("/page2");
        System.out.println(page2); // 输出页面2的内容
        String page1Cached = cache.getPageContent("/page1");
        System.out.println(page1Cached); // 输出缓存中页面1的内容,不再查询数据库
    }
}

在上面的示例中,通过cache字典来保存每个页面的内容,如果页面内容已经存在于缓存中,则直接从缓存中获取,否则从数据库中获取页面内容并放入缓存中。这样可以大大提高页面访问的速度和性能。

  • 场景二:数据库查询缓存

在应用程序中频繁地执行数据库查询是一种常见的性能瓶颈。为了减少对数据库的访问次数,提高系统的性能和响应速度,我们可以使用用户实现缓存来缓存数据库查询的结果。

  • Java代码示例:数据库查询结果缓存与查询响应优化

以下是一个简化的示例代码:

import java.util.HashMap;
import java.util.Map;

public class DatabaseQueryCache {
    private final Map<String, String> cache = new HashMap<>();

    public String executeQuery(String query) {
        String result = cache.get(query);
        if (result == null) {
            // 如果缓存中不存在查询结果,则执行数据库查询,并将结果放入缓存中
            result = executeDatabaseQuery(query);
            cache.put(query, result);
        }
        return result;
    }

    private String executeDatabaseQuery(String query) {
        // 执行数据库查询的逻辑
        // 这里为了简化示例,直接返回一个假的查询结果
        return "Result for query: " + query;
    }

    public static void main(String[] args) {
        DatabaseQueryCache cache = new DatabaseQueryCache();
        String result1 = cache.executeQuery("SELECT * FROM table1");
        System.out.println(result1); // 输出第一次查询的结果
        String result2 = cache.executeQuery("SELECT * FROM table2");
        System.out.println(result2); // 输出第二次查询的结果
        String result1Cached = cache.executeQuery("SELECT * FROM table1");
        System.out.println(result1Cached); // 输出缓存中第一次查询的结果,不再执行数据库查询
    }
}

在上面的示例中,通过cache字典来保存每次数据库查询的结果,如果查询结果已经存在于缓存中,则直接从缓存中获取,否则执行数据库查询并将结果放入缓存中。这样可以减少对数据库的访问次数,提高系统的性能和响应速度。

  • 场景三:API响应缓存

许多应用程序依赖于外部API来获取数据,但是对外部API的频繁调用可能会导致性能下降和额外的成本。在这种情况下,我们可以使用用户实现缓存来缓存API的响应数据,以减少对外部API的调用次数,提高系统的性能和可靠性。

Java代码示例:API响应缓存与调用优化

import java.util.HashMap;
import java.util.Map;

public class ApiCache {
    private final Map<String, String> cache = new HashMap<>();

    public String getApiResponse(String endpoint) {
        String response = cache.get(endpoint);
        if (response == null) {
            // 如果缓存中不存在API响应数据,则调用外部API,并将响应数据放入缓存中
            response = callExternalApi(endpoint);
            cache.put(endpoint, response);
        }
        return response;
    }

    private String callExternalApi(String endpoint) {
        // 调用外部API的逻辑
        // 这里为了简化示例,直接返回一个假的API响应数据
        return "Response from API endpoint: " + endpoint;
    }

    public static void main(String[] args) {
        ApiCache cache = new ApiCache();
        String response1 = cache.getApiResponse("/api/endpoint1");
        System.out.println(response1); // 输出第一次API响应数据
        String response2 = cache.getApiResponse("/api/endpoint2");
        System.out.println(response2); // 输出第二次API响应数据
        String response1Cached = cache.getApiResponse("/api/endpoint1");
        System.out.println(response1Cached); // 输出缓存中第一次API响应数据,不再调用外部API
    }
}

在上面的示例中,我们通过cache字典来保存每个API端点的响应数据,如果响应数据已经存在于缓存中,则直接从缓存中获取,否则调用外部API并将响应数据放入缓存中。这样可以减少对外部API的调用次数,提高系统的性能和可靠性。

场景四:对象缓存

在许多应用程序中,对象的创建和销毁是一项开销较大的操作,特别是对于那些需要频繁使用的对象。为了提高系统的性能和效率,我们可以使用用户实现缓存来缓存对象的实例,避免重复创建和销毁对象。

Java代码示例:对象缓存与重用优化

import java.util.HashMap;
import java.util.Map;

public class ObjectCache {
    private final Map<String, Object> cache = new HashMap<>();

    public Object getObject(String key) {
        Object obj = cache.get(key);
        if (obj == null) {
            // 如果缓存中不存在对象实例,则创建新的对象并放入缓存中
            obj = createObject(key);
            cache.put(key, obj);
        }
        return obj;
    }

    private Object createObject(String key) {
        // 创建对象实例的逻辑
        // 这里为了简化示例,直接返回一个假的对象实例
        return "Object instance for key: " + key;
    }

    public static void main(String[] args) {
        ObjectCache cache = new ObjectCache();
        Object obj1 = cache.getObject("key1");
        System.out.println(obj1); // 输出第一次创建的对象实例
        Object obj2 = cache.getObject("key2");
        System.out.println(obj2); // 输出第二次创建的对象实例
        Object obj1Cached = cache.getObject("key1");
        System.out.println(obj1Cached); // 输出缓存中第一次创建的对象实例,不再创建新的对象
    }
}

在上面的示例中,通过cache字典来保存每个对象实例,如果对象实例已经存在于缓存中,则直接从缓存中获取,否则创建新的对象并将其放入缓存中。这样可以避免重复创建对象,提高系统的性能和效率。

标签:缓存,String,cache,System,API,LRU,惰性,public
From: https://blog.csdn.net/qq_51447496/article/details/136693410

相关文章

  • Spring核心思想之 AOP:如何影响DI并引入三级缓存解决DI中涉及代理的问题
    Spring中AOP的实现与在Spring核心思想之AOP:在自定义容器基础上实现AOP功能中实现的自定义AOP一样,采用后置处理器方式。在Spring的核心思想之DI:详解SpringDI循环依赖实现机制文中末尾提到了一个问题,为什么是三级缓存而不是二级。下面示例AOP是如何影响DI的?......
  • 如何配置极狐GitLab Runner Cache 缓存
    本文作者:徐晓伟GitLab是一个全球知名的一体化DevOps平台,很多人都通过私有化部署GitLab来进行源代码托管。极狐GitLab是GitLab在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。极狐GitLabRunnerCache缓存支持S3标准协议,如:OSS、OOS等等支持S3......
  • 动态缓存单个页面-vue3-实现思路
    状态管理定义-全局状态属性`keepNameArray``noKeepNameArray` (为数组)动态组件缓存设置<keep-alive:include="keepNameArray":exclude="noKeepNameArray"><component:is="Component"/></keep-alive>该文件获取keepNameArray和noKeepNameA......
  • k05_多级缓存
    进程caffenineLUAcentos不用安装lua脚本,因为里面集成有以下是循环遍历的方法。变量名可以随便写函数和判断OpenRestyOpenResty是一个基于Nginx的高性能Web平台首先要安装OpenResty的依赖开发库,执行命令:yuminstall-ypcre-developenssl-develgcc--skip-broken安......
  • k04_分布式缓存
    1.redis持久化RDB持久化RDB全称ResdisDatabaseBackupfile(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件成为RDB文件,默认是保存在当前运行目录saveAOF持久化AO......
  • k03_缓存
    缓存更新策略缓存穿透缓存雪崩缓存击穿锁Redisson缓存穿透:用户请求的数据在缓存中和数据库中都不存在,不断发起这样的请求,给数据库带来巨大压力缓存穿透的解决方案有哪些?缓存null值布隆过滤增强id复杂度,避免被猜测id规律做好数据的基础格式校验加强用户权限校验......
  • 固态硬盘有缓存和没缓存之间的区别在哪
    ​固态硬盘(SSD)已经成为现代计算机的重要组成部分,它们提供了比传统机械硬盘更快的读写速度,从而显著提升了操作系统的运行速度和应用程序的加载效率。其中,缓存(Cache)是固态硬盘中一个重要的元素,它对于硬盘的性能和速度有着显著的影响。那么,固态硬盘有缓存和没缓存有什么区别?哪个更......
  • 清除缓存问题 localStorage浏览器本地缓存需清除网站数据 sessionStorage 创建各自的
    localStorage浏览器本地缓存pc需清除网站数据,或者开启新的无痕网页移动端app需进入设置页面,点击“清除浏览数据”选项;pc端的开启无痕就相当于开启一个新的浏览器无痕项目,但是app端开启无痕不会自动先清除之前已保存的数据sessionStorage浏览器页面缓存Window.sessionStorage......
  • 【深度解析】'go build'缓存机制:揭秘Windows下缓慢的原因
    引言本文主要围绕gobuild的缓存hash计算与获取缓存文件来编写。  笔者是Windows系统用户,在gobuild或golist-export一些需要编译(但已存在编译缓存)场景下执行的很慢。网上有很多说法大多都是说关闭杀毒软件、关闭磁盘扫描等,并未清楚的描述为什么。  接下来我将围绕g......
  • [Redis] 02-缓存和数据库数据一致性问题
    经过一番排查,确认服务器的性能瓶颈是在数据库。给服务器加上Redis,让其作为数据库的缓存。这样,在客户端请求数据时,如果能在缓存中命中数据,那就查询缓存,不用再去查询数据库,从而减轻数据库的压力,提高服务器的性能。一、缓存模型二、数据库和缓存的数据不一致问题更新数据时,数据......