Google Guava 緩存工具使用詳解
Google Guava 緩存工具使用詳解
緩存工具
Guava提供了Cache接口和相關(guān)的類來支持緩存功能,它提供了高性能、線程安全的內(nèi)存緩存,可以用于優(yōu)化應(yīng)用程序的性能。
特點(diǎn):
- 自動(dòng)回收過期數(shù)據(jù)
- 支持緩存項(xiàng)的定時(shí)回收
- 支持緩存項(xiàng)的最大數(shù)量限制
- 支持緩存項(xiàng)的大小限制
- 支持緩存項(xiàng)的權(quán)重限制,簡化開發(fā)者實(shí)現(xiàn)基于容量的限制
- 提供了統(tǒng)計(jì)信息
相關(guān)接口類:
接口/類 | 描述 |
---|---|
Cache<K, V> | 接口表示一種能夠存儲(chǔ)鍵值對的緩存結(jié)構(gòu) |
LoadingCache<K, V> | 是 Cache 接口的子接口,用于在緩存中自動(dòng)加載緩存項(xiàng) |
CacheLoader<K, V> | 在使用 LoadingCache 時(shí)提供加載緩存項(xiàng)的邏輯 |
CacheBuilder | 用于創(chuàng)建 Cache 和 LoadingCache 實(shí)例的構(gòu)建器類 |
CacheStats | 用于表示緩存的統(tǒng)計(jì)信息,如命中次數(shù)、命中率、加載次數(shù)、存儲(chǔ)次數(shù)等 |
RemovalListener<K, V> | 用于監(jiān)聽緩存條目被移除的事件,并在條目被移除時(shí)執(zhí)行相應(yīng)的操作 |
使用示例:
public static void main(String[] args) throws Exception { // 創(chuàng)建Cache實(shí)例 LoadingCache<String, String> cache = CacheBuilder.newBuilder() .initialCapacity(2) // 設(shè)置初始容量 .concurrencyLevel(4) // 設(shè)置并發(fā)級別 .maximumSize(5) // 設(shè)置最大容量 // .maximumWeight(1000) // 設(shè)置最大權(quán)重 // .weigher((Weigher<String, String>) (k, v) -> v.length()) // 設(shè)置權(quán)重計(jì)算器 .expireAfterWrite(Duration.ofSeconds(3)) // 寫入后3秒過期 .expireAfterAccess(Duration.ofSeconds(20)) // 訪問后20秒過期 .refreshAfterWrite(Duration.ofSeconds(10)) // 寫入后自動(dòng)刷新,3秒刷新一次 .recordStats() // 開啟統(tǒng)計(jì)信息記錄 .removalListener(notification -> { // 設(shè)置移除監(jiān)聽 // 緩存Key被移除時(shí)觸發(fā) String cause = ""; if (RemovalCause.EXPLICIT.equals(notification.getCause())) { cause = "被顯式移除"; } else if (RemovalCause.REPLACED.equals(notification.getCause())) { cause = "被替換"; } else if (RemovalCause.EXPIRED.equals(notification.getCause())) { cause = "被過期移除"; } else if (RemovalCause.SIZE.equals(notification.getCause())) { cause = "被緩存條數(shù)超上限移除"; } else if (RemovalCause.COLLECTED.equals(notification.getCause())) { cause = "被垃圾回收移除"; } System.out.println(DateUtil.formatDateTime(new Date()) + " Key: " + notification.getKey() + " 移除了, 移除原因: " + cause); }) .build(new CacheLoader<String, String>() { // 設(shè)置緩存重新加載邏輯 @Override public String load(String key) { // 重新加載指定Key的值 String newValue = "value" + (int)Math.random()*100; System.out.println(DateUtil.formatDateTime(new Date()) + " Key: " + key + " 重新加載,新value:" + newValue); return newValue; } }); // 將數(shù)據(jù)放入緩存 cache.put("key0", "value0"); cache.invalidate("key0"); cache.put("key1", "value1"); cache.put("key1", "value11"); cache.put("key2", "value22"); cache.put("key3", "value3"); cache.put("key4", "value4"); cache.put("key5", "value5"); cache.put("key6", "value6"); cache.put("key7", "value7"); cache.put("key8", "value8"); while (true) { // 獲取數(shù)據(jù) System.out.println(DateUtil.formatDateTime(new Date()) + " get key1 value: " + cache.get("key1")); // 統(tǒng)計(jì)信息 System.out.println(DateUtil.formatDateTime(new Date()) + " get stats: " + cache.stats()); Thread.sleep(1000); } }
打印日志:
2023-11-24 15:48:17 Key: key0 移除了, 移除原因: 被顯式移除
2023-11-24 15:48:17 Key: key1 移除了, 移除原因: 被替換
2023-11-24 15:48:17 Key: key1 移除了, 移除原因: 被緩存條數(shù)超上限移除
2023-11-24 15:48:17 Key: key2 移除了, 移除原因: 被緩存條數(shù)超上限移除
2023-11-24 15:48:17 Key: key3 移除了, 移除原因: 被緩存條數(shù)超上限移除
2023-11-24 15:48:17 Key: key1 重新加載,新value:value0
2023-11-24 15:48:17 Key: key4 移除了, 移除原因: 被緩存條數(shù)超上限移除
2023-11-24 15:48:17 get key1 value: value0
2023-11-24 15:48:17 get stats: CacheStats{hitCount=0, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:18 get key1 value: value0
2023-11-24 15:48:18 get stats: CacheStats{hitCount=1, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:19 get key1 value: value0
2023-11-24 15:48:19 get stats: CacheStats{hitCount=2, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:20 Key: key5 移除了, 移除原因: 被過期移除
2023-11-24 15:48:20 Key: key6 移除了, 移除原因: 被過期移除
2023-11-24 15:48:20 Key: key7 移除了, 移除原因: 被過期移除
2023-11-24 15:48:20 Key: key8 移除了, 移除原因: 被過期移除
2023-11-24 15:48:20 Key: key1 移除了, 移除原因: 被過期移除
2023-11-24 15:48:20 Key: key1 重新加載,新value:value0
2023-11-24 15:48:20 get key1 value: value0
2023-11-24 15:48:20 get stats: CacheStats{hitCount=2, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:21 get key1 value: value0
2023-11-24 15:48:21 get stats: CacheStats{hitCount=3, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:22 get key1 value: value0
2023-11-24 15:48:22 get stats: CacheStats{hitCount=4, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:23 Key: key1 移除了, 移除原因: 被過期移除
2023-11-24 15:48:23 Key: key1 重新加載,新value:value0
2023-11-24 15:48:23 get key1 value: value0
2023-11-24 15:48:23 get stats: CacheStats{hitCount=4, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
2023-11-24 15:48:24 get key1 value: value0
2023-11-24 15:48:24 get stats: CacheStats{hitCount=5, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
2023-11-24 15:48:25 get key1 value: value0
2023-11-24 15:48:25 get stats: CacheStats{hitCount=6, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
......
Cache接口
Cache接口是Guava緩存的核心接口,定義了緩存的基本操作。
它是使用 CacheBuilder 創(chuàng)建的。它提供了基本的緩存操作,如 put、get、delete等方法。同時(shí),Cache 還提供了諸如統(tǒng)計(jì)信息、緩存項(xiàng)的值獲取方式、緩存項(xiàng)的失效、緩存項(xiàng)的回收等方法,可以滿足大多數(shù)應(yīng)用的需求。
主要方法:
方法 | 描述 |
---|---|
V get(K key, Callable<? extends V> valueLoader) | 根據(jù)鍵獲取對應(yīng)的緩存值,如果緩存中不存在該鍵,會(huì)使用 valueLoader 加載并存儲(chǔ)該值。 |
V getIfPresent(K key) | 根據(jù)鍵獲取對應(yīng)的緩存值,如果不存在則返回 null。 |
Map<K, V> getAllPresent(Iterable<?> keys) | 獲取多個(gè)鍵對應(yīng)的緩存值的映射,如果緩存中不存在某個(gè)鍵,則該鍵不會(huì)出現(xiàn)在返回的映射中。 |
void put(K key, V value) | 將鍵值對放入緩存中。如果鍵已經(jīng)存在,則會(huì)替換對應(yīng)的值。 |
void putAll(Map<? extends K, ? extends V> map) | 將多個(gè)鍵值對添加到緩存中。 |
void invalidate(Object key) | 根據(jù)鍵從緩存中移除條目。 |
void invalidateAll(Iterable<?> keys) | 根據(jù)鍵集合移除多個(gè)條目。 |
void invalidateAll() | 移除緩存中的所有條目。 |
long size() | 返回緩存中的條目數(shù)。 |
CacheStats stats() | 返回緩存的統(tǒng)計(jì)信息。 |
ConcurrentMap<K, V> asMap() | 返回緩存的并發(fā)映射視圖。 |
void cleanUp() | 執(zhí)行緩存的清理操作。 |
LoadingCache接口
LoadingCache 繼承自 Cache 接口,它是一個(gè)帶有自動(dòng)加載功能的緩存接口。
在使用 LoadingCache 時(shí),如果緩存中不存在所需的鍵值對,則會(huì)自動(dòng)調(diào)用CacheLoader的加載方法進(jìn)行加載,并將加載的結(jié)果存入緩存中。
主要方法:
方法 | 說明 |
---|---|
get(K key) | 根據(jù)指定的鍵檢索值,如果鍵不存在,將調(diào)用 CacheLoader 進(jìn)行加載并返回對應(yīng)的值 |
getAll(Iterable<? extends K> keys) | 根據(jù)給定的鍵集合批量檢索值,并返回一個(gè) Map 對象,對于已緩存的鍵將直接返回對應(yīng)的值,對于未緩存的鍵將通過 CacheLoader 進(jìn)行加載。 |
getUnchecked(K key) | 獲取指定鍵對應(yīng)的值,如果緩存中不存在該鍵,則返回 null,不會(huì)觸發(fā)CacheLoader 加載。 |
refresh(K key) | 刷新指定鍵對應(yīng)的值,即使用 CacheLoader 重新加載該鍵對應(yīng)的值,并更新緩存。 |
CacheBuilder類
CacheBuilder類是用于創(chuàng)建Guava緩存的構(gòu)建器??梢允褂迷擃惖膎ewBuilder()方法創(chuàng)建一個(gè)構(gòu)建器實(shí)例,并通過一系列方法設(shè)置緩存的屬性,例如最大容量、過期時(shí)間等。最后可以通過build()方法構(gòu)建一個(gè)Cache實(shí)例。
主要方法:
方法 | 說明 |
---|---|
newBuilder() | 創(chuàng)建一個(gè)新的 CacheBuilder 實(shí)例 |
from(CacheBuilderSpec spec) | 根據(jù)給定的規(guī)范字符串創(chuàng)建一個(gè) CacheBuilder 實(shí)例 |
from(String spec) | 根據(jù)給定的規(guī)范字符串創(chuàng)建一個(gè) CacheBuilder 實(shí)例 |
initialCapacity(int initialCapacity) | 設(shè)置緩存的初始容量 |
concurrencyLevel(int concurrencyLevel) | 設(shè)置并發(fā)級別,用于估計(jì)同時(shí)寫入的線程數(shù) |
maximumSize(long maximumSize) | 設(shè)置緩存的最大容量 |
maximumWeight(long maximumWeight) | 設(shè)置緩存的最大權(quán)重 |
weigher(Weigher<? super K1, ? super V1> weigher) | 設(shè)置緩存的權(quán)重計(jì)算器 |
weakKeys() | 使用弱引用存儲(chǔ)緩存鍵(例如,鍵的引用沒有被其他對象引用時(shí),可以被垃圾回收) |
weakValues() | 使用弱引用存儲(chǔ)緩存值(例如,值的引用沒有被其他對象引用時(shí),可以被垃圾回收) |
softValues() | 使用軟引用存儲(chǔ)緩存值(例如,當(dāng)內(nèi)存不足時(shí),可以被垃圾回收) |
expireAfterWrite(java.time.Duration duration) | 設(shè)置寫入后過期時(shí)間 |
expireAfterWrite(long duration, TimeUnit unit) | 設(shè)置寫入后過期時(shí)間 |
expireAfterAccess(java.time.Duration duration) | 設(shè)置訪問后過期時(shí)間 |
expireAfterAccess(long duration, TimeUnit unit) | 設(shè)置訪問后過期時(shí)間 |
refreshAfterWrite(java.time.Duration duration) | 設(shè)置寫入后自動(dòng)刷新時(shí)間 |
refreshAfterWrite(long duration, TimeUnit unit) | 設(shè)置寫入后自動(dòng)刷新時(shí)間 |
ticker(Ticker ticker) | 設(shè)置用于衡量緩存時(shí)間的時(shí)鐘源 |
removalListener(RemovalListener<? super K1, ? super V1> listener) | 設(shè)置緩存條目移除監(jiān)聽器 |
recordStats() | 開啟緩存統(tǒng)計(jì)信息記錄 |
build(CacheLoader<? super K1, V1> loader) | 使用指定的 CacheLoader 構(gòu)建緩存 |
build() | 構(gòu)建緩存,如果沒有指定 CacheLoader,則需要使用 get 方法手動(dòng)加載緩存項(xiàng) |
部分方法詳解:
initialCapacity:設(shè)置緩存的初始容量
這個(gè)方法將通過一個(gè)整數(shù)值設(shè)置緩存的初始大小。它是一個(gè)可選的方法,如果沒有指定,緩存將采用默認(rèn)的初始容量。
concurrencyLevel:設(shè)置并發(fā)級別
用于估計(jì)同時(shí)寫入的線程數(shù)。這個(gè)方法將通過一個(gè)整數(shù)值設(shè)置并發(fā)級別,用于內(nèi)部數(shù)據(jù)結(jié)構(gòu)的調(diào)整,以提高并發(fā)寫入的性能。它是一個(gè)可選的方法,缺省值為 4。
maximumSize:設(shè)置緩存的最大容量
這個(gè)方法將通過一個(gè) long 類型的值設(shè)置緩存的最大容量。當(dāng)緩存的條目數(shù)達(dá)到這個(gè)容量時(shí),會(huì)觸發(fā)緩存清除策略來移除一些條目以騰出空間。它是一個(gè)可選的方法,如果沒有指定最大容量,緩存將不會(huì)有大小限制。
maximumWeight:設(shè)置緩存的最大權(quán)重
這個(gè)方法將通過一個(gè) long 類型的值設(shè)置緩存的最大權(quán)重。權(quán)重可以根據(jù)緩存條目的大小計(jì)算,通常用于緩存對象大小不同的場景。當(dāng)緩存的總權(quán)重達(dá)到這個(gè)值時(shí),會(huì)觸發(fā)緩存清除策略來移除一些條目以騰出空間。它是一個(gè)可選的方法,如果沒有指定最大權(quán)重,緩存將不會(huì)有權(quán)重限制。
weigher:設(shè)置緩存的權(quán)重計(jì)算器
這個(gè)方法將通過一個(gè)實(shí)現(xiàn)了 Weigher 接口的對象設(shè)置緩存的權(quán)重計(jì)算器。通過權(quán)重計(jì)算器,可以根據(jù)緩存條目的鍵和值來計(jì)算它們的權(quán)重,以便在達(dá)到最大權(quán)重時(shí)觸發(fā)緩存清除策略。它是一個(gè)可選的方法,如果沒有設(shè)置權(quán)重計(jì)算器,緩存將不會(huì)有權(quán)重限制。
expireAfterWrite:設(shè)置寫入后過期時(shí)間
這個(gè)方法通過一個(gè) java.time.Duration 對象設(shè)置緩存條目的寫入后過期時(shí)間。過期時(shí)間從最后一次寫入條目開始計(jì)算。一旦超過指定的時(shí)間,條目將被認(rèn)為是過期的并被清除。這是一個(gè)可選的方法,如果沒有指定過期時(shí)間,條目將不會(huì)主動(dòng)過期。
expireAfterAccess:設(shè)置訪問后過期時(shí)間
這個(gè)方法通過一個(gè) java.time.Duration 對象設(shè)置緩存條目的訪問后過期時(shí)間。過期時(shí)間從最后一次訪問條目開始計(jì)算。一旦超過指定的時(shí)間,條目將被認(rèn)為是過期的并被清除。這是一個(gè)可選的方法,如果沒有指定過期時(shí)間,條目將不會(huì)主動(dòng)過期。
refreshAfterWrite:設(shè)置寫入后自動(dòng)刷新時(shí)間
這個(gè)方法通過一個(gè) java.time.Duration 對象設(shè)置緩存條目的自動(dòng)刷新時(shí)間。自動(dòng)刷新時(shí)間從最后一次寫入條目開始計(jì)算。一旦超過指定的時(shí)間,當(dāng)條目被訪問時(shí),緩存將自動(dòng)刷新該條目,即會(huì)調(diào)用 CacheLoader 的 load 方法重新加載該條目。這是一個(gè)可選的方法,如果沒有設(shè)置自動(dòng)刷新時(shí)間,條目將不會(huì)自動(dòng)刷新。
recordStats():開啟緩存統(tǒng)計(jì)信息記錄
這個(gè)方法用于開啟緩存的統(tǒng)計(jì)信息記錄功能。一旦開啟,可以通過 Cache.stats() 方法獲取緩存的統(tǒng)計(jì)信息,如命中率、加載次數(shù)、平均加載時(shí)間等。這是一個(gè)可選的方法,如果不開啟統(tǒng)計(jì)信息記錄,將無法獲取緩存的統(tǒng)計(jì)信息。
注意事項(xiàng):
- maximumSize與maximumWeight不能同時(shí)設(shè)置
- 設(shè)置maximumWeight時(shí)必須設(shè)置weigher
- 當(dāng)緩存失效后,refreshAfterWrite設(shè)置的寫入后自動(dòng)刷新時(shí)間不會(huì)再有用
- 注意:expireAfterWrite、expireAfterAccess、refreshAfterWrite三個(gè)值的使用
- 開啟recordStats后,才進(jìn)行統(tǒng)計(jì)
CacheLoader類
CacheLoader 可以被視為一種從存儲(chǔ)系統(tǒng)(如磁盤、數(shù)據(jù)庫或遠(yuǎn)程節(jié)點(diǎn))中加載數(shù)據(jù)的方法。
CacheLoader 通常搭配refreshAfterWrite使用,在寫入指定的時(shí)間周期后會(huì)調(diào)用CacheLoader 的load方法來獲取并刷新為新值。
load 方法在以下情況下會(huì)被觸發(fā)調(diào)用:
- 當(dāng)設(shè)置了refreshAfterWrite(寫入后自動(dòng)刷新時(shí)間),達(dá)到自動(dòng)刷新時(shí)間時(shí),會(huì)調(diào)用 load 方法來重新加載該鍵的值。
- 調(diào)用 Cache.get(key) 方法獲取緩存中指定鍵的值時(shí),如果該鍵的值不存在,則會(huì)調(diào)用 load 方法來加載該鍵的值。
- 調(diào)用 Cache.get(key, callable) 方法獲取緩存中指定鍵的值時(shí),如果該鍵的值存在,則直接返回;如果該鍵的值不存在,則會(huì)調(diào)用 callable 參數(shù)指定的回調(diào)函數(shù)來計(jì)算并加載該鍵的值。
- 調(diào)用 Cache.getUnchecked(key) 方法獲取緩存中指定鍵的值時(shí),無論該鍵的值存在與否,都會(huì)調(diào)用 load 方法來加載該鍵的值。
需要注意的是,當(dāng)調(diào)用 load 方法加載緩存值時(shí),可能會(huì)發(fā)生 IO 操作或其他耗時(shí)操作,因此建議在加載操作中使用異步方式來避免阻塞主線程。另外,加載操作的實(shí)現(xiàn)要考慮緩存的一致性和并發(fā)性,避免多個(gè)線程同時(shí)加載同一個(gè)鍵的值。
CacheStats類
CacheStats 對象提供了諸如緩存命中率、加載緩存項(xiàng)數(shù)、緩存項(xiàng)回收數(shù)等統(tǒng)計(jì)信息的訪問。
它可以通過 Cache.stats() 方法來獲取,從而方便開發(fā)者監(jiān)控緩存狀態(tài)。
主要屬性:
屬性 | 描述 |
---|---|
hitCount | 緩存命中次數(shù)。表示從緩存中成功獲取數(shù)據(jù)的次數(shù) |
missCount | 緩存未命中次數(shù)。表示從緩存中未能獲取到數(shù)據(jù)的次數(shù) |
loadSuccessCount | 加載數(shù)據(jù)成功次數(shù)。表示通過 CacheLoader 成功加載數(shù)據(jù)的次數(shù) |
loadExceptionCount | 加載數(shù)據(jù)異常次數(shù)。表示通過 CacheLoader 加載數(shù)據(jù)時(shí)發(fā)生異常的次數(shù) |
totalLoadTime | 加載數(shù)據(jù)總耗時(shí)。表示通過 CacheLoader 加載數(shù)據(jù)的總時(shí)間 |
evictionCount | 緩存項(xiàng)被移除的次數(shù),只記錄因空超過設(shè)置的最大容量而進(jìn)行緩存項(xiàng)移除的次數(shù) |
RemovalListener類
RemovalListener 用于在緩存中某個(gè)值被移除時(shí)執(zhí)行相應(yīng)的回調(diào)操作。
可以使用 CacheBuilder.removalListener() 方法為緩存設(shè)置 RemovalListener。
RemovalListener 的使用:
- 創(chuàng)建一個(gè)實(shí)現(xiàn) RemovalListener 接口的類,實(shí)現(xiàn) onRemoval 方法。這個(gè)方法會(huì)在緩存項(xiàng)被移除時(shí)被調(diào)用,接受兩個(gè)參數(shù): key 和 value。key 是被移除的緩存項(xiàng)的鍵,value 是被移除的緩存項(xiàng)的值。你可以根據(jù)需要在 onRemoval 方法中實(shí)現(xiàn)自定義的邏輯。
- 使用 CacheBuilder 的 removalListener 方法,將創(chuàng)建的 RemovalListener 對象傳遞給它。
- 緩存項(xiàng)被移除時(shí),onRemoval 方法會(huì)自動(dòng)被調(diào)用,方法會(huì)傳入一個(gè)RemovalNotification 類型的參數(shù),里面包含相應(yīng)的 key 和 value等信息。你可以在這個(gè)方法中執(zhí)行自定義的業(yè)務(wù)邏輯,例如日志記錄、資源清理等操作。
RemovalNotification:
RemovalNotification 是 Guava 中用于表示緩存項(xiàng)被移除的通知的類。當(dāng)在 Guava Cache 中注冊了 RemovalListener 后,RemovalNotification 對象會(huì)在緩存項(xiàng)被移除時(shí)傳遞給 RemovalListener 的 onRemoval 方法。
RemovalNotification 包含了有關(guān)被移除緩存項(xiàng)的一些重要信息,例如鍵、值以及移除原因。下面是 RemovalNotification 類中常用的屬性和方法:
- getKey():獲取被移除的緩存項(xiàng)的鍵。
- getValue():獲取被移除的緩存項(xiàng)的值。
- getCause():獲取移除原因,它是一個(gè)枚舉類型RemovalCause,表示緩存項(xiàng)被移除的原因。
RemovalCause的可選值:
- EXPLICIT:條目被顯式刪除,例如通過調(diào)用 Cache.invalidate(key) 方法。
- REPLACED:條目被替換,例如通過調(diào)用 Cache.put(key, value) 方法重復(fù)放入相同的鍵。
- EXPIRED:緩存條目由于達(dá)到了指定的過期時(shí)間而被移除。
- SIZE:緩存條目由于超過了指定的大小限制而被移除。
- COLLECTED:緩存條目被垃圾回收移除。這是在啟用了緩存值的弱引用或軟引用時(shí)發(fā)生的。
使用 RemovalNotification 可以讓你在緩存項(xiàng)被移除時(shí)獲取相關(guān)信息,并根據(jù)移除原因采取適當(dāng)?shù)奶幚泶胧?。例如,你可以根?jù)移除原因記錄日志、執(zhí)行清理操作、發(fā)送通知等。這樣能夠增強(qiáng)緩存的功能和可觀察性。
注意事項(xiàng):
- RemovalListener 的 onRemoval 方法會(huì)在移除操作發(fā)生時(shí)同步調(diào)用,因此請確保不要在此方法中做耗時(shí)的操作,以免阻塞緩存的性能。
- 如果在緩存移除過程中拋出任何異常,它將被捕獲并記錄,不會(huì)影響緩存的正常運(yùn)行。
- 需要注意的是,RemovalListener 只會(huì)在通過 Cache 的操作(如 invalidate、invalidateAll、put 進(jìn)行替換)觸發(fā)移除時(shí)被調(diào)用,并不會(huì)在緩存項(xiàng)因?yàn)檫^期失效而自動(dòng)移除時(shí)被調(diào)用。
使用 RemovalListener 可以方便地在緩存項(xiàng)被移除時(shí)執(zhí)行一些自定義的操作,例如清理相關(guān)資源、更新其他緩存或發(fā)送通知等。根據(jù)實(shí)際需要,合理利用 RemovalListener 可以增強(qiáng)緩存的功能和靈活性。
補(bǔ)充:
Guava自加載緩存LoadingCache使用指南實(shí)戰(zhàn)案例
第1章:引言
大家好,我是小黑,今天我們來聊聊緩存。在Java世界里,高效的緩存機(jī)制對于提升應(yīng)用性能、降低數(shù)據(jù)庫負(fù)擔(dān)至關(guān)重要。想象一下,如果每次數(shù)據(jù)請求都要跑到數(shù)據(jù)庫里取,那服務(wù)器豈不是要累趴了?這時(shí)候,緩存就顯得尤為重要了。
那么,怎么實(shí)現(xiàn)一個(gè)既高效又好用的緩存呢?別急,咱們今天的主角——Guava的LoadingCache就是這樣一個(gè)神器。LoadingCache,顧名思義,就是能夠自動(dòng)加載緩存的工具。它不僅能自動(dòng)載入數(shù)據(jù),還能按需刷新,簡直是懶人救星!接下來,小黑就帶大家一起深入探究Guava的這個(gè)強(qiáng)大功能。
第2章:Guava簡介
Guava是Google開源的一款Java庫,提供了一堆好用的工具類,從集合操作、緩存機(jī)制到函數(shù)式編程,應(yīng)有盡有。使用Guava,咱們可以寫出更簡潔、更高效、更優(yōu)雅的Java代碼。今天,小黑重點(diǎn)要聊的是Guava中的緩存部分。
首先,讓我們來看看Guava緩存的一個(gè)基本概念:LoadingCache。LoadingCache是Guava中一個(gè)提供自動(dòng)加載功能的緩存接口。它允許咱們通過一個(gè)CacheLoader來指定如何加載緩存。這就意味著,當(dāng)咱們嘗試從緩存中讀取一個(gè)值,如果這個(gè)值不存在,LoadingCache就會(huì)自動(dòng)調(diào)用預(yù)定義的加載機(jī)制去獲取數(shù)據(jù),然后將其加入到緩存中,非常智能。
來,小黑先給大家展示一個(gè)簡單的LoadingCache創(chuàng)建示例:
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; public class LoadingCacheExample { public static void main(String[] args) { LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) // 最大緩存項(xiàng)數(shù) .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { return "Hello, " + key; // 定義緩存加載的方式 } }); System.out.println(cache.getUnchecked("Guava")); // 輸出:Hello, Guava } }
在這個(gè)例子里,小黑創(chuàng)建了一個(gè)簡單的LoadingCache實(shí)例。當(dāng)咱們嘗試通過getUnchecked
方法獲取一個(gè)緩存項(xiàng)時(shí),如果這個(gè)項(xiàng)不存在,CacheLoader會(huì)自動(dòng)加載一個(gè)新值。在這里,它就是簡單地返回一個(gè)字符串。
第3章:LoadingCache基礎(chǔ)
什么是LoadingCache呢?簡單來說,它是Guava提供的一個(gè)緩存接口,能夠自動(dòng)加載緩存。當(dāng)你嘗試從緩存中讀取一個(gè)值時(shí),如果這個(gè)值不存在,LoadingCache會(huì)自動(dòng)調(diào)用預(yù)定義的加載邏輯來獲取這個(gè)值,然后存儲(chǔ)到緩存中。這個(gè)過程完全自動(dòng)化,省去了很多手動(dòng)管理緩存的麻煩。
那么,LoadingCache的核心特性是什么呢?首先,它提供了自動(dòng)的緩存加載機(jī)制,這意味著咱們不需要自己去寫代碼判斷緩存是否存在或者過期。其次,它支持多種緩存過期策略,比如基于時(shí)間的過期、大小限制等,確保緩存的有效性。再者,LoadingCache還提供了緩存統(tǒng)計(jì)和監(jiān)聽的功能,方便咱們監(jiān)控和調(diào)優(yōu)緩存的使用。
來,讓小黑用一個(gè)例子來展示一下LoadingCache的基本用法:
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.concurrent.ExecutionException; public class LoadingCacheDemo { public static void main(String[] args) throws ExecutionException { // 創(chuàng)建一個(gè)CacheLoader CacheLoader<String, String> loader = new CacheLoader<String, String>() { @Override public String load(String key) { return key.toUpperCase(); // 模擬加載數(shù)據(jù)的過程 } }; // 使用CacheBuilder構(gòu)建一個(gè)LoadingCache LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) // 設(shè)置最大緩存數(shù)為100 .build(loader); // 使用緩存 System.out.println(cache.get("hello")); // 輸出: HELLO System.out.println(cache.get("guava")); // 輸出: GUAVA } }
在這個(gè)例子中,小黑創(chuàng)建了一個(gè)CacheLoader來定義加載數(shù)據(jù)的邏輯,這里就是簡單地將字符串轉(zhuǎn)換為大寫。然后,使用CacheBuilder來構(gòu)建一個(gè)LoadingCache實(shí)例,設(shè)置了最大緩存數(shù)為100。當(dāng)調(diào)用get
方法時(shí),如果緩存中不存在對應(yīng)的鍵值,LoadingCache會(huì)自動(dòng)調(diào)用CacheLoader來加載數(shù)據(jù),并將結(jié)果存入緩存。
第4章:創(chuàng)建LoadingCache
創(chuàng)建一個(gè)LoadingCache最關(guān)鍵的就是定義一個(gè)CacheLoader
。這個(gè)CacheLoader
指定了如何加載緩存。它就像是個(gè)工廠,當(dāng)咱們請求的數(shù)據(jù)在緩存中不存在時(shí),它就會(huì)生產(chǎn)出所需的數(shù)據(jù)。
那么,怎么定義這個(gè)CacheLoader
呢?讓小黑給你看個(gè)例子:
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; public class UserCache { // 假設(shè)有一個(gè)用戶服務(wù),用于獲取用戶信息 private static UserService userService = new UserService(); public static void main(String[] args) throws Exception { // 創(chuàng)建CacheLoader CacheLoader<String, User> loader = new CacheLoader<String, User>() { @Override public User load(String userId) { // 從用戶服務(wù)獲取用戶信息 return userService.getUserById(userId); } }; // 創(chuàng)建LoadingCache LoadingCache<String, User> cache = CacheBuilder.newBuilder() .maximumSize(100) // 設(shè)置最大緩存數(shù) .build(loader); // 使用緩存獲取用戶信息 User user = cache.get("123"); // 如果緩存中沒有,會(huì)調(diào)用load方法加載數(shù)據(jù) System.out.println(user); } }
在這個(gè)例子中,小黑創(chuàng)建了一個(gè)CacheLoader
來從用戶服務(wù)中獲取用戶信息。然后,使用CacheBuilder
來構(gòu)建一個(gè)LoadingCache
,并設(shè)置了最大緩存數(shù)量為100。當(dāng)咱們通過get
方法獲取用戶信息時(shí),如果緩存中沒有相應(yīng)的數(shù)據(jù),CacheLoader
就會(huì)自動(dòng)加載數(shù)據(jù)。
這個(gè)過程聽起來是不是很神奇?實(shí)際上,這背后是一種非常有效的數(shù)據(jù)管理策略。通過這種方式,咱們可以減少對數(shù)據(jù)庫或遠(yuǎn)程服務(wù)的直接訪問,提高了應(yīng)用的響應(yīng)速度和效率。
第5章:LoadingCache的高級特性 自動(dòng)加載和刷新機(jī)制
首先,LoadingCache的一個(gè)很棒的功能就是自動(dòng)加載和刷新。這意味著當(dāng)咱們請求某個(gè)鍵的值時(shí),如果這個(gè)值不存在或者需要刷新,LoadingCache會(huì)自動(dòng)調(diào)用CacheLoader
去加載或刷新數(shù)據(jù)。
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.concurrent.TimeUnit; public class AutoRefreshCache { public static void main(String[] args) throws Exception { LoadingCache<String, String> cache = CacheBuilder.newBuilder() .refreshAfterWrite(1, TimeUnit.MINUTES) // 設(shè)置1分鐘后刷新 .build(new CacheLoader<String, String>() { @Override public String load(String key) { return fetchDataFromDatabase(key); // 模擬從數(shù)據(jù)庫加載數(shù)據(jù) } }); // 使用緩存 System.out.println(cache.get("key1")); // 第一次加載 // 1分鐘后,嘗試再次獲取,將觸發(fā)刷新操作 } private static String fetchDataFromDatabase(String key) { // 模擬數(shù)據(jù)庫操作 return "Data for " + key; } }
在這個(gè)例子中,咱們設(shè)置了refreshAfterWrite
,這意味著每當(dāng)一個(gè)鍵值對寫入一分鐘后,它就會(huì)被自動(dòng)刷新。
處理異常值
有時(shí)候,加載數(shù)據(jù)可能會(huì)出現(xiàn)異常。LoadingCache提供了優(yōu)雅的處理異常的機(jī)制。
public class ExceptionHandlingCache { public static void main(String[] args) throws Exception { LoadingCache<String, String> cache = CacheBuilder.newBuilder() .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { if ("errorKey".equals(key)) { throw new Exception("Loading error"); } return "Data for " + key; } }); try { System.out.println(cache.get("errorKey")); } catch (Exception e) { System.out.println("Error during cache load: " + e.getMessage()); } } }
這里,如果加載過程中出現(xiàn)異常,咱們可以捕獲這個(gè)異常,并做適當(dāng)?shù)奶幚怼?/p>
統(tǒng)計(jì)和監(jiān)聽功能
LoadingCache還提供了緩存統(tǒng)計(jì)和監(jiān)聽功能,這對于監(jiān)控緩存性能和行為非常有用。
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; public class CacheMonitoring { public static void main(String[] args) throws Exception { RemovalListener<String, String> removalListener = new RemovalListener<String, String>() { @Override public void onRemoval(RemovalNotification<String, String> notification) { System.out.println("Removed: " + notification.getKey() + ", Cause: " + notification.getCause()); } }; LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) .removalListener(removalListener) .build(new CacheLoader<String, String>() { // ... }); // 使用緩存 // ... } }
在這個(gè)例子中,小黑設(shè)置了一個(gè)RemovalListener
,用于監(jiān)聽緩存項(xiàng)的移除事件。
第6章:LoadingCache的最佳實(shí)踐 配置緩存大小
合理配置緩存大小非常關(guān)鍵。如果緩存太小,就會(huì)頻繁地加載數(shù)據(jù),影響性能;如果太大,又可能消耗過多內(nèi)存。
LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(1000) // 設(shè)置最大緩存項(xiàng)為1000 .build(new CacheLoader<String, String>() { // ... });
在這個(gè)例子中,小黑設(shè)置了最大緩存項(xiàng)為1000。這個(gè)值需要根據(jù)實(shí)際情況和資源限制來調(diào)整。
設(shè)置合適的過期策略
LoadingCache支持基于時(shí)間的過期策略,比如訪問后過期和寫入后過期。
LoadingCache<String, String> cache = CacheBuilder.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) // 寫入后10分鐘過期 .expireAfterAccess(5, TimeUnit.MINUTES) // 訪問后5分鐘過期 .build(new CacheLoader<String, String>() { // ... });
選擇合適的過期策略可以確保緩存中的數(shù)據(jù)既不會(huì)過時(shí),又能有效利用內(nèi)存。
異常處理策略
在加載數(shù)據(jù)時(shí)可能會(huì)遇到各種異常。咱們可以設(shè)置一個(gè)合理的異常處理策略,比如記錄日志、返回默認(rèn)值或者重新拋出異常。
LoadingCache<String, String> cache = CacheBuilder.newBuilder() .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { try { return fetchData(key); } catch (Exception e) { // 處理異常 } } });
使用軟引用或弱引用
為了防止緩存占用過多內(nèi)存,可以使用軟引用或弱引用。
LoadingCache<String, String> cache = CacheBuilder.newBuilder() .softValues() // 使用軟引用存儲(chǔ)值 .weakKeys() // 使用弱引用存儲(chǔ)鍵 .build(new CacheLoader<String, String>() { // ... });
使用軟引用和弱引用可以幫助Java垃圾收集器在需要時(shí)回收緩存項(xiàng),防止內(nèi)存泄露。
監(jiān)聽移除通知
設(shè)置移除監(jiān)聽器可以幫助咱們了解緩存的行為,比如為什么某個(gè)項(xiàng)被移除。
LoadingCache<String, String> cache = CacheBuilder.newBuilder() .removalListener(notification -> { // 處理移除通知 }) .build(new CacheLoader<String, String>() { // ... });
通過這些最佳實(shí)踐,咱們可以確保LoadingCache的高效運(yùn)行,同時(shí)避免一些常見的問題。這樣,咱們的Java應(yīng)用就能更加穩(wěn)定和高效地運(yùn)行啦!
第7章:LoadingCache與Java 8的結(jié)合
好的,咱們接下來聊聊怎樣把LoadingCache和Java 8的特性結(jié)合起來,用起來更順手。
Java 8引入了很多強(qiáng)大的新特性,像Lambda表達(dá)式、Stream API等,這些都可以和LoadingCache搭配使用,讓代碼更簡潔、更易讀。
使用Lambda表達(dá)式簡化CacheLoader
首先,咱們可以用Lambda表達(dá)式來簡化CacheLoader的創(chuàng)建。這樣代碼看起來更干凈,更直觀。
LoadingCache<String, String> cache = CacheBuilder.newBuilder() .build(key -> fetchDataFromDatabase(key)); // 使用Lambda表達(dá)式 private static String fetchDataFromDatabase(String key) { // 數(shù)據(jù)庫操作 return "Data for " + key; }
在這個(gè)例子里,小黑用Lambda表達(dá)式替代了傳統(tǒng)的匿名內(nèi)部類,使代碼更加簡潔。
結(jié)合Stream API處理緩存數(shù)據(jù)
接下來,咱們看看如何用Java 8的Stream API來處理LoadingCache中的數(shù)據(jù)。
LoadingCache<String, User> userCache = //... 緩存初始化 List<String> userIds = //... 用戶ID列表 // 使用Stream API獲取用戶信息列表 List<User> users = userIds.stream() .map(userId -> userCache.getUnchecked(userId)) .collect(Collectors.toList());
在這個(gè)例子中,小黑用Stream API來處理一系列用戶ID,然后用map
方法從緩存中獲取對應(yīng)的用戶信息。
利用Optional處理緩存返回值
最后,Java 8引入的Optional也可以用來優(yōu)雅地處理可能為空的緩存返回值。
public Optional<User> getUser(String userId) { try { return Optional.ofNullable(userCache.get(userId)); } catch (Exception e) { return Optional.empty(); } }
在這里,小黑用Optional包裝了緩存的返回值。這樣一來,就能優(yōu)雅地處理緩存可能返回的空值情況。
通過這些方式,結(jié)合Java 8的特性,咱們可以讓LoadingCache的使用更加高效和優(yōu)雅。這不僅提高了代碼的可讀性,還讓咱們的編程體驗(yàn)更加流暢。
第8章:實(shí)戰(zhàn)案例
小黑將通過一個(gè)具體的例子,展示如何在實(shí)際項(xiàng)目中使用LoadingCache。這個(gè)例子會(huì)模擬一個(gè)簡單的場景,比如說,使用LoadingCache來緩存用戶的登錄次數(shù)。
假設(shè)咱們有一個(gè)應(yīng)用,需要跟蹤用戶的登錄次數(shù)。每次用戶登錄時(shí),程序會(huì)增加其登錄次數(shù)。為了提高性能,咱們用LoadingCache來緩存這些數(shù)據(jù),避免每次都查詢數(shù)據(jù)庫。
首先,小黑定義了一個(gè)模擬的用戶登錄服務(wù):
public Optional<User> getUser(String userId) { try { return Optional.ofNullable(userCache.get(userId)); } catch (Exception e) { return Optional.empty(); } }
這個(gè)UserService
類有一個(gè)addLoginCount
方法,用于增加特定用戶的登錄次數(shù)。
接下來,小黑將展示如何使用LoadingCache來緩存登錄次數(shù):
LoadingCache<String, Integer> loginCache = CacheBuilder.newBuilder() .expireAfterAccess(30, TimeUnit.MINUTES) // 設(shè)置緩存30分鐘后過期 .build(new CacheLoader<String, Integer>() { @Override public Integer load(String userId) { return userService.addLoginCount(userId); } }); public void userLogin(String userId) { int count = loginCache.getUnchecked(userId); System.out.println("User " + userId + " login count: " + count); }
在這個(gè)例子中,每當(dāng)有用戶登錄,userLogin
方法就會(huì)被調(diào)用。這個(gè)方法會(huì)從loginCache
中獲取用戶的登錄次數(shù),如果緩存中沒有,CacheLoader
會(huì)調(diào)用UserService
的addLoginCount
來獲取最新的計(jì)數(shù),然后將其存儲(chǔ)在緩存中。
第9章:總結(jié)
通過這些章節(jié),咱們了解了LoadingCache的基本原理和用法,包括如何創(chuàng)建和配置緩存,以及如何結(jié)合Java 8的特性來優(yōu)化代碼。LoadingCache不僅提供了自動(dòng)加載和刷新的強(qiáng)大功能,還有異常處理、緩存統(tǒng)計(jì)和監(jiān)聽等高級特性。
實(shí)戰(zhàn)案例給咱們展示了LoadingCache在現(xiàn)實(shí)場景中的應(yīng)用。不管是緩存用戶信息還是統(tǒng)計(jì)數(shù)據(jù),LoadingCache都能大大提高性能和用戶體驗(yàn)。
到此這篇關(guān)于Guava自加載緩存LoadingCache使用指南的文章就介紹到這了,更多相關(guān)Guava LoadingCache緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring MVC @GetMapping和@PostMapping注解的使用方式
這篇文章主要介紹了Spring MVC @GetMapping和@PostMapping注解的使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05解決Springboot項(xiàng)目報(bào)錯(cuò):java:錯(cuò)誤:不支持發(fā)行版本?17
這篇文章主要給大家介紹了關(guān)于解決Springboot項(xiàng)目報(bào)錯(cuò):java:錯(cuò)誤:不支持發(fā)行版本17的相關(guān)資料,這個(gè)錯(cuò)誤意味著你的Spring Boot項(xiàng)目正在使用Java 17這個(gè)版本,但是你的項(xiàng)目中未配置正確的Java版本,需要的朋友可以參考下2023-08-08java基于QuartzJobBean實(shí)現(xiàn)定時(shí)功能的示例代碼
QuartzJobBean是Quartz框架中的一個(gè)抽象類,用于定義和實(shí)現(xiàn)可由Quartz調(diào)度的作業(yè),本文主要介紹了java基于QuartzJobBean實(shí)現(xiàn)定時(shí)功能的示例代碼,具有一定的參考價(jià)值,感興趣可以了解一下2023-09-09SpringBoot中的application.properties無法加載問題定位技巧
這篇文章主要介紹了SpringBoot中的application.properties無法加載問題定位技巧,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05