Java使用map实现带过期时间的缓存机制

 更新时间:2025年04月27日 08:29:06   作者:小太阳381  
在 Java 开发领域,缓存机制的构建通常依赖于 Redis 等专业缓存数据库,这类解决方案虽能提供强大的缓存能力,但引入中间件意味着增加系统架构复杂度、部署成本与运维负担,本文将深入探讨一种轻量级替代方案基于 Java 原生Map实现的带过期时间的缓存机制

引言

在 Java 开发领域,缓存机制的构建通常依赖于 Redis 等专业缓存数据库。这类解决方案虽能提供强大的缓存能力,但引入中间件意味着增加系统架构复杂度、部署成本与运维负担。本文将深入探讨一种轻量级替代方案 —— 基于 Java 原生Map实现的带过期时间的缓存机制。该方案无需引入外部工具,仅依托 Java 标准库即可快速搭建起缓存体系,特别适用于对资源占用敏感、架构追求极简的项目场景,为开发者提供了一种轻量高效的缓存数据管理新选择。

优点

  • 轻量便捷:无需引入 Redis 等外部中间件,直接使用 Java 标准库即可实现,降低了项目依赖,简化了部署流程。
  • 快速搭建:基于熟悉的Map数据结构,开发人员能够快速理解和实现缓存逻辑,显著提升开发效率。
  • 资源可控:可灵活控制缓存数据的生命周期,通过设置过期时间,精准管理内存占用,适合对资源占用敏感的场景。

缺点:该方案存在明显局限性,即数据无法持久化。一旦应用程序停止运行,缓存中的所有数据都会丢失。相较于 Redis 等具备持久化功能的专业缓存数据库,在需要长期保存缓存数据,或是应对应用重启后数据恢复需求的场景下,基于 Java 原生Map的缓存机制就显得力不从心。

代码实现

package com.sunny.utils;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class SysCache {

    // 单例实例
    private static class Holder {
        private static final SysCache INSTANCE = new SysCache();
    }

    public static SysCache getInstance() {
        return Holder.INSTANCE;
    }

    // 缓存存储结构,Key为String,Value为包含值和过期时间的CacheEntry对象
    private final ConcurrentHashMap<String, CacheEntry> cacheMap = new ConcurrentHashMap<>();
    // 定时任务执行器
    private final ScheduledExecutorService  scheduledExecutorService;

    // 私有构造方法,初始化定时清理任务
    private SysCache() {
        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        // 每隔1秒执行一次清理任务
        scheduledExecutorService.scheduleAtFixedRate(this::cleanUp, 1, 1, TimeUnit.SECONDS);
        // 注册JVM关闭钩子以优雅关闭线程池
        Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
    }

    /**
     * 存入缓存
     * @param key 键
     * @param value 值
     */
    public void set(String key, Object value){
        cacheMap.put(key, new CacheEntry(value, -1));
    }

    /**
     * 存入缓存
     * @param key 键
     * @param value 值
     * @param expireTime 过期时间,单位毫秒
     */
    public void set(String key, Object value, long expireTime) {
        if (expireTime <= 0) {
            throw new IllegalArgumentException("expireTime must be greater than 0");
        }
        cacheMap.put(key, new CacheEntry(value, System.currentTimeMillis() + expireTime));
    }

    /**
     * 删除缓存
     * @param key 键
     */
    public void remove(String key) {
        cacheMap.remove(key);
    }

    /**
     * 缓存中是否包含键
     * @param key 键
     */
    public boolean containsKey(String key) {
        CacheEntry cacheEntry = cacheMap.get(key);
        if (cacheEntry == null) {
            return false;
        }
        if (cacheEntry.getExpireTime() < System.currentTimeMillis()) {
            remove(key);
            return false;
        }
        return true;
    }


    /**
     *获取缓存值
     * @param key 键
     */
    public Object get(String key) {
        CacheEntry cacheEntry = cacheMap.get(key);
        if (cacheEntry == null) {
            return null;
        }
        if (cacheEntry.getExpireTime() < System.currentTimeMillis()) {
            cacheMap.remove(key);
            return null;
        }
        return cacheEntry.getValue();
    }


    private static class CacheEntry {
        private final Object value;
        private final long expireTime;

        public CacheEntry(Object value, long expireTime) {
            this.value = value;
            this.expireTime = expireTime;
        }

        public Object getValue() {
            return value;
        }

        public long getExpireTime() {
            return expireTime;
        }
    }

    /**
     * 定时清理过期条目
     */
    private void cleanUp() {
        Iterator<Map.Entry<String, CacheEntry>> iterator = cacheMap.entrySet().iterator();
        long currentTime = System.currentTimeMillis();
        while (iterator.hasNext()) {
            Map.Entry<String, CacheEntry> entry = iterator.next();
            CacheEntry cacheEntry = entry.getValue();
            if (cacheEntry.expireTime < currentTime) {
                // 使用iterator移除当前条目,避免ConcurrentModificationException
                iterator.remove();
            }
        }
    }

    /**
     * 关闭线程池释放资源
     */
    private void shutdown() {
        scheduledExecutorService.shutdown();
        try {
            if (!scheduledExecutorService.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduledExecutorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduledExecutorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

测试

如上图,缓存中放入一个值,过期时间为5秒,每秒循环获取1次,循环10次,过期后,获取的值为null

到此这篇关于Java使用map实现带过期时间的缓存机制的文章就介绍到这了,更多相关Java map过期时间缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决Maven多模块编译慢的问题

    解决Maven多模块编译慢的问题

    这篇文章主要介绍了Maven多模块编译慢的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Java日期工具类操作字符串Date和LocalDate互转

    Java日期工具类操作字符串Date和LocalDate互转

    这篇文章主要介绍了Java日期工具类操作字符串Date和LocalDate互转,文章首先通过需要先引入坐标展开主题的相关内容介绍,需要的朋友可以参一下
    2022-06-06
  • spring security如何扩展自定义登录

    spring security如何扩展自定义登录

    本文详细介绍了Spring Security的认证原理和具体实现,认证原理基于过滤器链,通过验证用户凭证和构建认证对象来保护应用程序资源,实现自定义认证功能的步骤包括创建自定义认证提供程序、实现UserDetailsService接口以及在配置类中进行相应的配置
    2024-11-11
  • 浅谈标签和JLabel类构造方法

    浅谈标签和JLabel类构造方法

    这篇文章主要介绍了标签和JLabel类构造方法,具有一定参考价值,需要的朋友可以参考下。
    2017-09-09
  • Java Socket实现简易聊天室

    Java Socket实现简易聊天室

    这篇文章主要为大家详细介绍了Java Socket实现简易聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-03-03
  • Java中的Sort排序问题

    Java中的Sort排序问题

    这篇文章主要介绍了Java中的Sort排序问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • 使用Java8实现模板方法模式的改造

    使用Java8实现模板方法模式的改造

    这篇文章主要为大家详细介绍了如何使用Java8实现模板方法模式的改造,文中的示例代码讲解详细,对我们深入了解java8有一定的帮助,感兴趣的可以了了解一下
    2023-01-01
  • IDEA自定义常用代码块及自定义快捷摸板

    IDEA自定义常用代码块及自定义快捷摸板

    这篇文章主要介绍了IDEA自定义常用代码块及自定义快捷摸板的相关知识,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-09-09
  • 测量Java对象所占内存大小方式

    测量Java对象所占内存大小方式

    这篇文章主要介绍了测量Java对象所占内存大小方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • Java 十大排序算法之计数排序刨析

    Java 十大排序算法之计数排序刨析

    计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法
    2021-11-11

最新评论