Java如何設(shè)置過期時(shí)間的map的幾種方法
一、技術(shù)背景
在實(shí)際的項(xiàng)目開發(fā)中,我們經(jīng)常會(huì)使用到緩存中間件(如redis、MemCache等)來幫助我們提高系統(tǒng)的可用性和健壯性。
但是很多時(shí)候如果項(xiàng)目比較簡(jiǎn)單,就沒有必要為了使用緩存而專門引入Redis等等中間件來加重系統(tǒng)的復(fù)雜性。那么Java本身有沒有好用的輕量級(jí)的緩存組件呢。
答案當(dāng)然是有嘍,而且方法不止一種。常見的解決方法有:ExpiringMap、LoadingCache及基于HashMap的封裝三種。
二、技術(shù)效果
- 實(shí)現(xiàn)緩存的常見功能,如過時(shí)刪除策略
- 熱點(diǎn)數(shù)據(jù)預(yù)熱
三、ExpiringMap
3.1 功能簡(jiǎn)介
- 可設(shè)置Map中的Entry在一段時(shí)間后自動(dòng)過期。
- 可設(shè)置Map最大容納值,當(dāng)?shù)竭_(dá)Maximum size后,再次插入值會(huì)導(dǎo)致Map中的第一個(gè)值過期。
- 可添加監(jiān)聽事件,在監(jiān)聽到Entry過期時(shí)調(diào)度監(jiān)聽函數(shù)。
- 可以設(shè)置懶加載,在調(diào)用get()方法時(shí)創(chuàng)建對(duì)象。
3.2 源碼
3.3 示例
添加依賴(Maven)
<dependency>? ? ? <groupId>net.jodah</groupId>? ? ? <artifactId>expiringmap</artifactId>? ? ? <version>0.5.8</version>? </dependency>?
示例源碼
public class ExpiringMapApp { ?? ?public static void main(String[] args) { ?? ??? ?// maxSize: 設(shè)置最大值,添加第11個(gè)entry時(shí),會(huì)導(dǎo)致第1個(gè)立馬過期(即使沒到過期時(shí)間) ?? ??? ?// expiration:設(shè)置每個(gè)key有效時(shí)間10s, 如果key不設(shè)置過期時(shí)間,key永久有效。 ?? ??? ?// variableExpiration: 允許更新過期時(shí)間值,如果不設(shè)置variableExpiration,不允許后面更改過期時(shí)間,一旦執(zhí)行更改過期時(shí)間操作會(huì)拋異常UnsupportedOperationException ?? ??? ?// policy: ?? ??? ?// ? ? ? ?CREATED: 只在put和replace方法清零過期時(shí)間 ?? ??? ?// ? ? ? ?ACCESSED: 在CREATED策略基礎(chǔ)上增加, 在還沒過期時(shí)get方法清零過期時(shí)間。 ?? ??? ?// ? ? ? ?清零過期時(shí)間也就是重置過期時(shí)間,重新計(jì)算過期時(shí)間. ?? ??? ?ExpiringMap<String, String> map = ExpiringMap.builder() ?? ??? ??? ?.maxSize(10) ?? ??? ??? ?.expiration(10, TimeUnit.SECONDS) ?? ??? ??? ?.variableExpiration().expirationPolicy(ExpirationPolicy.CREATED).build(); ?? ??? ?map.put("token", "lkj2412lj1412412nmlkjl2n34l23n4"); ?? ??? ?map.put("name", "管理員", 20000, TimeUnit.SECONDS); ?? ??? ?// 模擬線程等待... ?? ??? ?try { ?? ??? ??? ?Thread.sleep(15000); ?? ??? ?} catch (InterruptedException e) { ?? ??? ??? ?e.printStackTrace(); ?? ??? ?} ?? ??? ?System.out.println("token ===> " + map.get("token")); ?? ??? ?System.out.println("name ===> " + map.get("name")); ?? ??? ?// 注意: 在創(chuàng)建map時(shí),指定的那些參數(shù)如過期時(shí)間和過期策略都是全局的, 對(duì)map中添加的每一個(gè)entry都適用. ?? ??? ?// ? ? ? ?在put一個(gè)entry鍵值對(duì)時(shí)可以對(duì)當(dāng)前entry 單獨(dú)設(shè)置 過期時(shí)間、過期策略,只對(duì)當(dāng)前這個(gè)entry有效. ?? ?} }
運(yùn)行結(jié)果
token ===> null
name ===> 管理員
注意
在創(chuàng)建map時(shí),指定的那些參數(shù)如過期時(shí)間和過期策略都是全局的, 對(duì)map中添加的每一個(gè)entry都適用。
在put一個(gè)entry鍵值對(duì)時(shí)可以對(duì)當(dāng)前entry 單獨(dú)設(shè)置 過期時(shí)間、過期策略,只對(duì)當(dāng)前這個(gè)entry有效.
四、LoadingCache
4.1 功能簡(jiǎn)介
Google開源出來的一個(gè)線程安全的本地緩存解決方案。
特點(diǎn):提供緩存回收機(jī)制,監(jiān)控緩存加載/命中情況,靈活強(qiáng)大的功能,簡(jiǎn)單易上手的api。
4.2 示例
源碼
public class LoadingCacheApp { ?? ?public static void main(String[] args) throws Exception { ?? ??? ?// maximumSize: 緩存池大小,在緩存項(xiàng)接近該大小時(shí), Guava開始回收舊的緩存項(xiàng) ?? ??? ?// expireAfterAccess: 設(shè)置時(shí)間對(duì)象沒有被讀/寫訪問則對(duì)象從內(nèi)存中刪除(在另外的線程里面不定期維護(hù)) ?? ??? ?// removalListener: 移除監(jiān)聽器,緩存項(xiàng)被移除時(shí)會(huì)觸發(fā)的鉤子 ?? ??? ?// recordStats: 開啟Guava Cache的統(tǒng)計(jì)功能 ?? ??? ?LoadingCache<String, String> cache = CacheBuilder.newBuilder() ?? ??? ??? ?.maximumSize(100) ?? ??? ??? ?.expireAfterAccess(10, TimeUnit.SECONDS) ?? ??? ??? ?.removalListener(new RemovalListener<String, String>() { ?? ??? ??? ??? ?@Override ?? ??? ??? ??? ?public void onRemoval(RemovalNotification<String, String> removalNotification) { ?? ??? ??? ??? ??? ?System.out.println("過時(shí)刪除的鉤子觸發(fā)了... key ===> " + removalNotification.getKey()); ?? ??? ??? ??? ?} ?? ??? ??? ?}) ?? ??? ??? ?.recordStats() ?? ??? ??? ?.build(new CacheLoader<String, String>() { ?? ??? ??? ??? ?// 處理緩存鍵不存在緩存值時(shí)的處理邏輯 ?? ??? ??? ??? ?@Override ?? ??? ??? ??? ?public String load(String key) throws Exception { ?? ??? ??? ??? ??? ?return "不存在的key"; ?? ??? ??? ??? ?} ?? ??? ??? ?}); ?? ??? ?cache.put("name", "小明"); ?? ??? ?cache.put("pwd", "112345"); ?? ??? ?// 模擬線程等待... ?? ??? ?try { ?? ??? ??? ?Thread.sleep(15000); ?? ??? ?} catch (InterruptedException e) { ?? ??? ??? ?e.printStackTrace(); ?? ??? ?} ?? ??? ?System.out.println("token ===> " + cache.get("name")); ?? ??? ?System.out.println("name ===> " + cache.get("pwd")); ?? ?} }
運(yùn)行結(jié)果
過時(shí)刪除的鉤子觸發(fā)了... key ===> name
token ===> 不存在的key
過時(shí)刪除的鉤子觸發(fā)了... key ===> pwd
name ===> 不存在的key
4.3 移除機(jī)制
guava做cache時(shí)候數(shù)據(jù)的移除分為被動(dòng)移除和主動(dòng)移除兩種。
被動(dòng)移除
- 基于大小的移除:數(shù)量達(dá)到指定大小,會(huì)把不常用的鍵值移除
- 基于時(shí)間的移除:expireAfterAccess(long, TimeUnit) 根據(jù)某個(gè)鍵值對(duì)最后一次訪問之后多少時(shí)間后移除。expireAfterWrite(long, TimeUnit) 根據(jù)某個(gè)鍵值對(duì)被創(chuàng)建或值被替換后多少時(shí)間移除
- 基于引用的移除:主要是基于java的垃圾回收機(jī)制,根據(jù)鍵或者值的引用關(guān)系決定移除
主動(dòng)移除
- 單獨(dú)移除:Cache.invalidate(key)
- 批量移除:Cache.invalidateAll(keys)
- 移除所有:Cache.invalidateAll()
如果配置了移除監(jiān)聽器RemovalListener,則在所有移除的動(dòng)作時(shí)會(huì)同步執(zhí)行該listener下的邏輯。
如需改成異步,使用:RemovalListeners.asynchronous(RemovalListener, Executor).
4.4 其他
- 在put操作之前,如果已經(jīng)有該鍵值,會(huì)先觸發(fā)removalListener移除監(jiān)聽器,再添加
- 配置了expireAfterAccess和expireAfterWrite,但在指定時(shí)間后沒有被移除。
- 刪除策略邏輯:
CacheBuilder構(gòu)建的緩存不會(huì)在特定時(shí)間自動(dòng)執(zhí)行清理和回收工作,也不會(huì)在某個(gè)緩存項(xiàng)過期后馬上清理,它不會(huì)啟動(dòng)一個(gè)線程來進(jìn)行緩存維護(hù),因?yàn)槭紫染€程相對(duì)較重,其次某些環(huán)境限制線程的創(chuàng)建。
它會(huì)在寫操作時(shí)順帶做少量的維護(hù)工作,或者偶爾在讀操作時(shí)做。當(dāng)然,也可以創(chuàng)建自己的維護(hù)線程,以固定的時(shí)間間隔調(diào)用Cache.cleanUp()。
五、HashMap的封裝
我們可以參考上面兩個(gè)工具包的思路,自己封裝一個(gè)可以設(shè)置過時(shí)時(shí)間的HashMap來實(shí)現(xiàn)我們想要的效果。
到此這篇關(guān)于Java如何設(shè)置過期時(shí)間的map的幾種方法的文章就介紹到這了,更多相關(guān)Java 過期時(shí)間map內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java8 forEach結(jié)合Lambda表達(dá)式遍歷 List操作
這篇文章主要介紹了java8 forEach結(jié)合Lambda表達(dá)式遍歷 List操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09Java FileInputStream與FileOutputStream使用詳解
這篇文章主要介紹了Java FileInputStream與FileOutputStream使用詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08Java實(shí)現(xiàn)ftp上傳下載、刪除文件及在ftp服務(wù)器上傳文件夾的方法
這篇文章主要介紹了Java實(shí)現(xiàn)ftp上傳下載、刪除文件及在ftp服務(wù)器上傳文件夾的方法,需要的朋友可以參考下2015-11-11java實(shí)現(xiàn)學(xué)生管理系統(tǒng)(面向?qū)ο?
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生管理系統(tǒng)(面向?qū)ο螅闹惺纠a介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03Springboot文件上傳出現(xiàn)找不到指定系統(tǒng)路徑的解決
這篇文章主要介紹了Springboot文件上傳出現(xiàn)找不到指定系統(tǒng)路徑的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08SpringBoot統(tǒng)計(jì)一個(gè)Bean中方法的調(diào)用次數(shù)的實(shí)現(xiàn)步驟
這篇文章主要給大家介紹了SpringBoot統(tǒng)計(jì)一個(gè)Bean中方法的調(diào)用次數(shù)的實(shí)現(xiàn)步驟,文中通過代碼示例和圖文結(jié)合的方式給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)具有一定的幫助,需要的朋友可以參考下2024-01-01深入理解Java定時(shí)調(diào)度(Timer)機(jī)制
這篇文章主要介紹了深入理解Java定時(shí)調(diào)度(Timer)機(jī)制,本節(jié)我們主要分析 Timer 的功能。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-01-01