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过期时间缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java编程实现NBA赛事接口调用实例代码

    Java编程实现NBA赛事接口调用实例代码

    这篇文章主要介绍了Java编程实现NBA赛事接口调用实例代码,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • struts2中simple主题下<s:fieldError>标签默认样式的移除方法

    struts2中simple主题下<s:fieldError>标签默认样式的移除方法

    这篇文章主要给大家介绍了关于struts2中simple主题下<s:fieldError>标签默认样式的移除方法,文中通过示例代码介绍的非常详细,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧
    2018-10-10
  • Java自定义一个变长数组的思路与代码

    Java自定义一个变长数组的思路与代码

    有时我们希望将把数据保存在单个连续的数组中,以便快速、便捷地访问数据,但这需要调整数组大小或者对其扩展,下面这篇文章主要给大家介绍了关于Java自定义一个变长数组的思路与代码,需要的朋友可以参考下
    2022-12-12
  • MybatisPlus处理大表查询的实现步骤

    MybatisPlus处理大表查询的实现步骤

    在实际工作中当指定查询数据过大时,我们一般使用分页查询的方式一页一页的将数据放到内存处理,本文主要介绍了MybatisPlus处理大表查询的实现步骤,感兴趣的可以了解一下
    2024-08-08
  • 使用spring拦截器实现日志管理实例

    使用spring拦截器实现日志管理实例

    本篇文章主要介绍了使用spring拦截器实现日志管理实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03
  • springboot war包部署过程详解

    springboot war包部署过程详解

    这篇文章主要为大家介绍了springboot war包部署过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08
  • 详解SpringBoot结合swagger2快速生成简单的接口文档

    详解SpringBoot结合swagger2快速生成简单的接口文档

    这篇文章主要介绍了详解SpringBoot结合swagger2快速生成简单的接口文档,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-05-05
  • 学习Java之IO流的基础概念详解

    学习Java之IO流的基础概念详解

    这篇文章主要给大家介绍了Java中的IO流,我们首先要搞清楚一件事,就是为什么需要IO流这个东西,但在正式学习IO流的使用之前,小编有必要带大家先了解一下IO流的基本概念,需要的朋友可以参考下
    2023-09-09
  • Java黑盒测试之nextDate函数测试

    Java黑盒测试之nextDate函数测试

    这篇文章主要介绍了Java黑盒测试之nextDate函数测试,文中有非常详细的代码示例,对正在学习Java黑盒测试的小伙伴们有很大的帮助哦,需要的朋友可以参考下
    2021-05-05
  • Java class文件格式之常量池_动力节点Java学院整理

    Java class文件格式之常量池_动力节点Java学院整理

    这篇文章主要为大家详细介绍了Java class文件格式之常量池的相关资料,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06

最新评论