Java使用map實(shí)現(xiàn)帶過期時間的緩存機(jī)制
引言
在 Java 開發(fā)領(lǐng)域,緩存機(jī)制的構(gòu)建通常依賴于 Redis 等專業(yè)緩存數(shù)據(jù)庫。這類解決方案雖能提供強(qiáng)大的緩存能力,但引入中間件意味著增加系統(tǒng)架構(gòu)復(fù)雜度、部署成本與運(yùn)維負(fù)擔(dān)。本文將深入探討一種輕量級替代方案 —— 基于 Java 原生Map實(shí)現(xiàn)的帶過期時間的緩存機(jī)制。該方案無需引入外部工具,僅依托 Java 標(biāo)準(zhǔn)庫即可快速搭建起緩存體系,特別適用于對資源占用敏感、架構(gòu)追求極簡的項(xiàng)目場景,為開發(fā)者提供了一種輕量高效的緩存數(shù)據(jù)管理新選擇。
優(yōu)點(diǎn):
- 輕量便捷:無需引入 Redis 等外部中間件,直接使用 Java 標(biāo)準(zhǔn)庫即可實(shí)現(xiàn),降低了項(xiàng)目依賴,簡化了部署流程。
- 快速搭建:基于熟悉的Map數(shù)據(jù)結(jié)構(gòu),開發(fā)人員能夠快速理解和實(shí)現(xiàn)緩存邏輯,顯著提升開發(fā)效率。
- 資源可控:可靈活控制緩存數(shù)據(jù)的生命周期,通過設(shè)置過期時間,精準(zhǔn)管理內(nèi)存占用,適合對資源占用敏感的場景。
缺點(diǎn):該方案存在明顯局限性,即數(shù)據(jù)無法持久化。一旦應(yīng)用程序停止運(yùn)行,緩存中的所有數(shù)據(jù)都會丟失。相較于 Redis 等具備持久化功能的專業(yè)緩存數(shù)據(jù)庫,在需要長期保存緩存數(shù)據(jù),或是應(yīng)對應(yīng)用重啟后數(shù)據(jù)恢復(fù)需求的場景下,基于 Java 原生Map的緩存機(jī)制就顯得力不從心。
代碼實(shí)現(xiàn)
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 {
// 單例實(shí)例
private static class Holder {
private static final SysCache INSTANCE = new SysCache();
}
public static SysCache getInstance() {
return Holder.INSTANCE;
}
// 緩存存儲結(jié)構(gòu),Key為String,Value為包含值和過期時間的CacheEntry對象
private final ConcurrentHashMap<String, CacheEntry> cacheMap = new ConcurrentHashMap<>();
// 定時任務(wù)執(zhí)行器
private final ScheduledExecutorService scheduledExecutorService;
// 私有構(gòu)造方法,初始化定時清理任務(wù)
private SysCache() {
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
// 每隔1秒執(zhí)行一次清理任務(wù)
scheduledExecutorService.scheduleAtFixedRate(this::cleanUp, 1, 1, TimeUnit.SECONDS);
// 注冊JVM關(guān)閉鉤子以優(yōu)雅關(guān)閉線程池
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移除當(dāng)前條目,避免ConcurrentModificationException
iterator.remove();
}
}
}
/**
* 關(guān)閉線程池釋放資源
*/
private void shutdown() {
scheduledExecutorService.shutdown();
try {
if (!scheduledExecutorService.awaitTermination(5, TimeUnit.SECONDS)) {
scheduledExecutorService.shutdownNow();
}
} catch (InterruptedException e) {
scheduledExecutorService.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
測試

如上圖,緩存中放入一個值,過期時間為5秒,每秒循環(huán)獲取1次,循環(huán)10次,過期后,獲取的值為null
到此這篇關(guān)于Java使用map實(shí)現(xiàn)帶過期時間的緩存機(jī)制的文章就介紹到這了,更多相關(guān)Java map過期時間緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java編程實(shí)現(xiàn)NBA賽事接口調(diào)用實(shí)例代碼
這篇文章主要介紹了Java編程實(shí)現(xiàn)NBA賽事接口調(diào)用實(shí)例代碼,具有一定參考價值,需要的朋友可以了解下。2017-11-11
struts2中simple主題下<s:fieldError>標(biāo)簽?zāi)J(rèn)樣式的移除方法
這篇文章主要給大家介紹了關(guān)于struts2中simple主題下<s:fieldError>標(biāo)簽?zāi)J(rèn)樣式的移除方法,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10
MybatisPlus處理大表查詢的實(shí)現(xiàn)步驟
在實(shí)際工作中當(dāng)指定查詢數(shù)據(jù)過大時,我們一般使用分頁查詢的方式一頁一頁的將數(shù)據(jù)放到內(nèi)存處理,本文主要介紹了MybatisPlus處理大表查詢的實(shí)現(xiàn)步驟,感興趣的可以了解一下2024-08-08
使用spring攔截器實(shí)現(xiàn)日志管理實(shí)例
本篇文章主要介紹了使用spring攔截器實(shí)現(xiàn)日志管理實(shí)例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03
詳解SpringBoot結(jié)合swagger2快速生成簡單的接口文檔
這篇文章主要介紹了詳解SpringBoot結(jié)合swagger2快速生成簡單的接口文檔,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05
學(xué)習(xí)Java之IO流的基礎(chǔ)概念詳解
這篇文章主要給大家介紹了Java中的IO流,我們首先要搞清楚一件事,就是為什么需要IO流這個東西,但在正式學(xué)習(xí)IO流的使用之前,小編有必要帶大家先了解一下IO流的基本概念,需要的朋友可以參考下2023-09-09
Java class文件格式之常量池_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java class文件格式之常量池的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06

