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