Java實(shí)現(xiàn)本地緩存的方式匯總
一、概念
引入緩存,主要用于實(shí)現(xiàn)系統(tǒng)的高性能,高并發(fā)。將數(shù)據(jù)庫查詢出來的數(shù)據(jù)放入緩存服務(wù)中,因?yàn)榫彺媸谴鎯υ趦?nèi)存中的,內(nèi)存的讀寫性能遠(yuǎn)超磁盤的讀寫性能,所以訪問的速度非???。但是電腦重啟后,內(nèi)存中的數(shù)據(jù)會(huì)全部清除,而磁盤中的數(shù)據(jù)雖然讀寫性能很差,但是數(shù)據(jù)不會(huì)丟失。
二、手寫本地緩存
首先創(chuàng)建一個(gè)緩存實(shí)體類
package com.example.vuespringboot.bean;
import lombok.Data;
/**
* @author qx
* @date 2023/7/27
* @des 自定義緩存實(shí)體類
*/
@Data
public class MyCache {
/**
* 鍵
*/
private String key;
/**
* 值
*/
private Object value;
/**
* 過期時(shí)間
*/
private Long expireTime;
}接著我們編寫一個(gè)緩存操作的工具類
package com.example.vuespringboot.util;
import com.example.vuespringboot.bean.MyCache;
import java.time.Duration;
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;
/**
* @author qx
* @date 2023/7/27
* @des 自定義本地緩存工具類
*/
public class CacheUtil {
/**
* 緩存數(shù)據(jù)Map
*/
private static final Map<String, MyCache> CACHE_MAP = new ConcurrentHashMap<>();
/**
* 定時(shí)器線程池,用于清除過期緩存
*/
private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
static {
// 注冊一個(gè)定時(shí)線程任務(wù),服務(wù)啟動(dòng)1秒之后,每隔500毫秒執(zhí)行一次
// 定時(shí)清理過期緩存
executorService.scheduleAtFixedRate(CacheUtil::clearCache, 1000, 500, TimeUnit.MILLISECONDS);
}
/**
* 添加緩存
*
* @param key 緩存鍵
* @param value 緩存值
* @param expire 過期時(shí)間,單位秒
*/
public static void put(String key, Object value, long expire) {
MyCache myCache = new MyCache();
myCache.setKey(key);
myCache.setValue(value);
if (expire > 0) {
long expireTime = System.currentTimeMillis() + Duration.ofSeconds(expire).toMillis();
myCache.setExpireTime(expireTime);
}
CACHE_MAP.put(key, myCache);
}
/**
* 獲取緩存
*
* @param key 緩存鍵
* @return 緩存數(shù)據(jù)
*/
public static Object get(String key) {
if (CACHE_MAP.containsKey(key)) {
return CACHE_MAP.get(key).getValue();
}
return null;
}
/**
* 移除緩存
*
* @param key 緩存鍵
*/
public static void remove(String key) {
CACHE_MAP.remove(key);
}
/**
* 清理過期的緩存數(shù)據(jù)
*/
private static void clearCache() {
if (CACHE_MAP.size() <= 0) {
return;
}
// 判斷是否過期 過期就從緩存Map刪除這個(gè)元素
CACHE_MAP.entrySet().removeIf(entry -> entry.getValue().getExpireTime() != null && entry.getValue().getExpireTime() > System.currentTimeMillis());
}
}最后,我們來測試一下緩存服務(wù)
package com.example.vuespringboot;
import com.example.vuespringboot.util.CacheUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
@SpringBootTest
class VueSpringBootApplicationTests {
@Test
void contextLoads() throws InterruptedException {
// 寫入緩存數(shù)據(jù) 2秒后過期
CacheUtil.put("name", "qx", 2);
Object value1 = CacheUtil.get("name");
System.out.println("第一次查詢結(jié)果:" + value1);
// 停頓3秒
TimeUnit.SECONDS.sleep(3);
Object value2 = CacheUtil.get("name");
System.out.println("第二次查詢結(jié)果:" + value2);
}
}啟動(dòng)測試,我們從控制臺的返回看到輸出結(jié)果和我們的預(yù)期一致!
第一次查詢結(jié)果:qx
第二次查詢結(jié)果:null
實(shí)現(xiàn)思路其實(shí)很簡單,采用ConcurrentHashMap作為緩存數(shù)據(jù)存儲服務(wù),然后開啟一個(gè)定時(shí)調(diào)度,每隔500毫秒檢查一下過期的緩存數(shù)據(jù),然后清除掉!
三、基于Guava Cache實(shí)現(xiàn)本地緩存
相比自己編寫的緩存服務(wù),Guava Cache 要強(qiáng)大的多,支持很多特性如下:
- 支持最大容量限制
- 支持兩種過期刪除策略(插入時(shí)間和讀取時(shí)間)
- 支持簡單的統(tǒng)計(jì)功能
- 基于 LRU 算法實(shí)現(xiàn)
1.添加gugva的依賴
<!--guava-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>2.測試
@Test
void testGuava() throws ExecutionException, InterruptedException {
// 創(chuàng)建一個(gè)緩存實(shí)例
Cache<String, String> cache = CacheBuilder.newBuilder()
// 初始容量
.initialCapacity(5)
// 最大緩存數(shù),超出淘汰
.maximumSize(10)
// 過期時(shí)間 設(shè)置寫入3秒后過期
.expireAfterWrite(3, TimeUnit.SECONDS)
.build();
// 寫入緩存數(shù)據(jù)
cache.put("name", "qq");
// 讀取緩存數(shù)據(jù)
String value1 = cache.get("name", () -> "key過期");
System.out.println("第一次查詢結(jié)果:" + value1);
// 停頓4秒
TimeUnit.SECONDS.sleep(4);
// 讀取緩存數(shù)據(jù)
String value2 = cache.get("name", () -> "key過期");
System.out.println("第二次查詢結(jié)果:" + value2);
}啟動(dòng)測試,我們從控制臺的返回看到輸出結(jié)果和我們的預(yù)期一致!
第一次查詢結(jié)果:qq
第二次查詢結(jié)果:key過期
四、基于 Caffeine 實(shí)現(xiàn)本地緩存
Caffeine 是基于 java8 實(shí)現(xiàn)的新一代緩存工具,緩存性能接近理論最優(yōu),可以看作是 Guava Cache 的增強(qiáng)版,功能上兩者類似,不同的是 Caffeine 采用了一種結(jié)合 LRU、LFU 優(yōu)點(diǎn)的算法:W-TinyLFU,在性能上有明顯的優(yōu)越性。
1.引入Caffeine
<!--caffeine-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.3</version>
</dependency>2.測試
@Test
void testCaffeine() throws InterruptedException {
// 創(chuàng)建一個(gè)緩存實(shí)例
Cache<String, String> cache = Caffeine.newBuilder()
// 初始容量
.initialCapacity(5)
// 最大緩存數(shù),超出淘汰
.maximumSize(10)
// 設(shè)置緩存寫入間隔多久過期
.expireAfterWrite(3, TimeUnit.SECONDS)
// 設(shè)置緩存最后訪問后間隔多久淘汰,實(shí)際很少用到
.build();
// 寫入緩存數(shù)據(jù)
cache.put("userName", "張三");
// 讀取緩存數(shù)據(jù)
String value1 = cache.get("userName", (key) -> {
// 如果key不存在,會(huì)執(zhí)行回調(diào)方法
return "key已過期";
});
System.out.println("第一次查詢結(jié)果:" + value1);
// 停頓4秒
Thread.sleep(4000);
// 讀取緩存數(shù)據(jù)
String value2 = cache.get("userName", (key) -> {
// 如果key不存在,會(huì)執(zhí)行回調(diào)方法
return "key已過期";
});
System.out.println("第二次查詢結(jié)果:" + value2);
}輸出結(jié)果:
第一次查詢結(jié)果:張三
第二次查詢結(jié)果:key已過期
五、基于 Encache 實(shí)現(xiàn)本地緩存
1.引入ehcache依賴
<!--ehcache-->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.7</version>
</dependency>2.自定義過期策略實(shí)現(xiàn)
package com.example.vuespringboot.util;
import org.ehcache.expiry.ExpiryPolicy;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
/**
* @author qx
* @date 2023/7/27
* @des 自定義過期策略實(shí)現(xiàn)
*/
public class CustomExpiryPolicy<K, V> implements ExpiryPolicy<K, V> {
private final Map<K, Duration> keyExpireMap = new ConcurrentHashMap();
public Duration setExpire(K key, Duration duration) {
return keyExpireMap.put(key, duration);
}
public Duration getExpireByKey(K key) {
return Optional.ofNullable(keyExpireMap.get(key))
.orElse(null);
}
public Duration removeExpire(K key) {
return keyExpireMap.remove(key);
}
@Override
public Duration getExpiryForCreation(K key, V value) {
return Optional.ofNullable(getExpireByKey(key))
.orElse(Duration.ofNanos(Long.MAX_VALUE));
}
@Override
public Duration getExpiryForAccess(K key, Supplier<? extends V> value) {
return getExpireByKey(key);
}
@Override
public Duration getExpiryForUpdate(K key, Supplier<? extends V> oldValue, V newValue) {
return getExpireByKey(key);
}
}3.測試
package com.example.vuespringboot.util;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import java.time.Duration;
/**
* @author qx
* @date 2023/7/27
* @des 測試Encache
*/
public class EncacheTest {
public static void main(String[] args) throws InterruptedException {
String userCache = "userCache";
// 自定義過期策略
CustomExpiryPolicy<Object, Object> customExpiryPolicy = new CustomExpiryPolicy<>();
// 聲明一個(gè)容量為20的堆內(nèi)緩存配置
CacheConfigurationBuilder configurationBuilder = CacheConfigurationBuilder
.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20))
.withExpiry(customExpiryPolicy);
// 初始化一個(gè)緩存管理器
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
// 創(chuàng)建cache實(shí)例
.withCache(userCache, configurationBuilder)
.build(true);
// 獲取cache實(shí)例
Cache<String, String> cache = cacheManager.getCache(userCache, String.class, String.class);
// 獲取過期策略
CustomExpiryPolicy expiryPolicy = (CustomExpiryPolicy) cache.getRuntimeConfiguration().getExpiryPolicy();
// 寫入緩存數(shù)據(jù)
cache.put("userName", "張三");
// 設(shè)置3秒過期
expiryPolicy.setExpire("userName", Duration.ofSeconds(3));
// 讀取緩存數(shù)據(jù)
String value1 = cache.get("userName");
System.out.println("第一次查詢結(jié)果:" + value1);
// 停頓4秒
Thread.sleep(4000);
// 讀取緩存數(shù)據(jù)
String value2 = cache.get("userName");
System.out.println("第二次查詢結(jié)果:" + value2);
}
}輸出結(jié)果:
第一次查詢結(jié)果:張三
第二次查詢結(jié)果:null
到此這篇關(guān)于Java實(shí)現(xiàn)本地緩存的幾種方式的文章就介紹到這了,更多相關(guān)java本地緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Android的應(yīng)用中實(shí)現(xiàn)網(wǎng)絡(luò)圖片異步加載的方法
這篇文章主要介紹了在Android的應(yīng)用中實(shí)現(xiàn)網(wǎng)絡(luò)圖片異步加載的方法,一定程度上有助于提高安卓程序的使用體驗(yàn),需要的朋友可以參考下2015-07-07
JAVA實(shí)現(xiàn)社會(huì)統(tǒng)一信用代碼校驗(yàn)的方法
這篇文章主要介紹了JAVA實(shí)現(xiàn)社會(huì)統(tǒng)一信用代碼校驗(yàn)的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
Spring Boot中實(shí)現(xiàn)定時(shí)任務(wù)應(yīng)用實(shí)踐
定時(shí)任務(wù)一般是項(xiàng)目中都需要用到的,可以用于定時(shí)處理一些特殊的任務(wù)。下面這篇文章主要給大家介紹了關(guān)于Spring Boot中實(shí)現(xiàn)定時(shí)任務(wù)應(yīng)用實(shí)踐的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。2018-05-05
使用Filter過濾器中訪問getSession()要轉(zhuǎn)化
這篇文章主要介紹了使用Filter過濾器中訪問getSession()要轉(zhuǎn)化,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java多線程實(shí)戰(zhàn)之單例模式與多線程的實(shí)例詳解
今天小編就為大家分享一篇關(guān)于Java多線程實(shí)戰(zhàn)之單例模式與多線程的實(shí)例詳解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02
詳解JavaEE 使用 Redis 數(shù)據(jù)庫進(jìn)行內(nèi)容緩存和高訪問負(fù)載
本篇文章主要介紹了JavaEE 使用 Redis 數(shù)據(jù)庫進(jìn)行內(nèi)容緩存和高訪問負(fù)載,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08

