Java實(shí)現(xiàn)本地緩存的四種方法實(shí)現(xiàn)與對比
本地緩存比如 caffine,guava cache 這些都是比較常用的,本地緩存的優(yōu)點(diǎn)就是速度非??欤瑳]有網(wǎng)絡(luò)消耗,缺點(diǎn)就是應(yīng)用重啟后,緩存就會丟失。
Java緩存技術(shù)可分為遠(yuǎn)端緩存和本地緩存,遠(yuǎn)端緩存常用的方案有著名的redis,而本地緩存的代表技術(shù)主要有HashMap,Guava Cache,Caffeine和Encahche。
1、HashMap
通過Map的底層方式,直接將需要緩存的對象放在內(nèi)存中。
- 優(yōu)點(diǎn):簡單粗暴,不需要引入第三方包,比較適合一些比較簡單的場景。
- 缺點(diǎn):沒有緩存淘汰策略,定制化開發(fā)成本高。
package com.taiyuan.javademoone.cachedemo; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 定義一個基于LinkedHashMap實(shí)現(xiàn)的線程安全的LRU緩存類 */ public class LRUCache extends LinkedHashMap<Object, Object> { // 明確泛型類型為<Object, Object>,提高代碼可讀性 /** * 可重入讀寫鎖,用于保證多線程環(huán)境下對緩存的并發(fā)讀寫操作的安全性 */ private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private Lock readLock = readWriteLock.readLock(); // 讀鎖,用于并發(fā)讀取時共享訪問 private Lock writeLock = readWriteLock.writeLock(); // 寫鎖,用于寫入或修改時獨(dú)占訪問 /** * 緩存的最大容量限制,超過此容量將移除最久未使用的條目 */ private int maxSize; /** * 構(gòu)造函數(shù),初始化LRU緩存并設(shè)置最大容量 * * @param maxSize 緩存允許存儲的最大條目數(shù) */ public LRUCache(int maxSize) { // 調(diào)用父類LinkedHashMap的構(gòu)造方法: // 參數(shù)1:初始容量為maxSize + 1,避免頻繁擴(kuò)容 // 參數(shù)2:負(fù)載因子為1.0f,表示哈希表填滿到100%時才擴(kuò)容 // 參數(shù)3:accessOrder為true,表示按照訪問順序排序,實(shí)現(xiàn)LRU策略 super(maxSize + 1, 1.0f, true); this.maxSize = maxSize; } /** * 重寫get方法,獲取指定key對應(yīng)的value,使用讀鎖保證線程安全 * * @param key 要查找的鍵 * @return 對應(yīng)的值,如果不存在則返回null */ @Override public Object get(Object key) { readLock.lock(); // 加讀鎖,允許多個線程同時讀 try { return super.get(key); // 調(diào)用父類的get方法 } finally { readLock.unlock(); // 確保讀鎖最終被釋放 } } /** * 重寫put方法,向緩存中添加或更新鍵值對,使用寫鎖保證線程安全 * * @param key 要插入或更新的鍵 * @param value 要插入或更新的值 * @return 之前與key關(guān)聯(lián)的值,如果沒有則返回null */ @Override public Object put(Object key, Object value) { writeLock.lock(); // 加寫鎖,確保同一時間只有一個線程可以寫 try { return super.put(key, value); // 調(diào)用父類的put方法 } finally { writeLock.unlock(); // 確保寫鎖最終被釋放 } } /** * 重寫removeEldestEntry方法,當(dāng)緩存大小超過maxSize時,移除最久未使用的條目 * * @param eldest 最久未訪問的鍵值對Entry * @return 如果當(dāng)前緩存大小超過最大容量,則返回true,觸發(fā)刪除最老的條目;否則返回false */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { return this.size() > maxSize; // 判斷當(dāng)前緩存大小是否超出限制 } // 測試主方法 public static void main(String[] args) { LRUCache cache = new LRUCache(3); cache.put("1", "one"); cache.put("2", "two"); cache.put("3", "three"); System.out.println(cache); // 輸出:{1=one, 2=two, 3=three} } }
2、Guava Cache
Guava Cache 是 Google Guava 庫中的一個本地緩存實(shí)現(xiàn),它提供了以下主要特性:
- 自動加載:當(dāng)緩存未命中時自動從指定來源加載數(shù)據(jù)
- 多種淘汰策略:支持基于大小、時間和引用的淘汰
- 統(tǒng)計功能:內(nèi)置緩存命中率統(tǒng)計
- 線程安全:內(nèi)置并發(fā)控制機(jī)制
- 監(jiān)聽器:支持緩存移除通知
<!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>33.4.8-jre</version> </dependency>
package com.helloworld.demo; import com.google.common.cache.*; import java.util.concurrent.TimeUnit; public class GuavaCacheExample { public static void main(String[] args) { // 創(chuàng)建一個Guava的LoadingCache實(shí)例,支持自動加載緩存項(xiàng) LoadingCache<String, String> cache = CacheBuilder.newBuilder() .maximumSize(100) // 設(shè)置緩存最大容量為100個條目 .expireAfterWrite(10, TimeUnit.MINUTES) // 設(shè)置緩存項(xiàng)在寫入10分鐘后自動過期 .recordStats() // 開啟緩存統(tǒng)計功能,可以獲取命中率等信息 .build(new CacheLoader<String, String>() { // 定義緩存未命中時的加載邏輯 @Override public String load(String key) throws Exception { // 當(dāng)根據(jù)key獲取不到緩存值時,調(diào)用此方法從數(shù)據(jù)源(如數(shù)據(jù)庫)加載數(shù)據(jù) return fetchDataFromDatabase(key); } }); try { // 第一次獲取key為"user:1001"的值,由于緩存中沒有,會觸發(fā)load方法從數(shù)據(jù)庫加載 System.out.println("第一次獲取(從數(shù)據(jù)庫加載): " + cache.get("user:1001")); // 第二次獲取相同的key,此時緩存中已有該值,直接從緩存返回,不會再次加載 System.out.println("第二次獲取(從緩存獲取): " + cache.get("user:1001")); // 手動向緩存中放入一個鍵值對,繞過自動加載邏輯 cache.put("user:1002", "Manual Data"); // 獲取手動放入的緩存值 System.out.println("手動放入的數(shù)據(jù): " + cache.get("user:1002")); // 打印緩存的統(tǒng)計信息,如命中率、加載次數(shù)等 System.out.println("\n緩存統(tǒng)計:"); System.out.println(cache.stats()); // 手動移除指定key的緩存項(xiàng) cache.invalidate("user:1001"); // 嘗試獲取已被移除的緩存項(xiàng),返回null表示不存在 System.out.println("\n移除后獲取: " + cache.getIfPresent("user:1001")); } catch (Exception e) { e.printStackTrace(); // 捕獲并打印異常信息 } } // 模擬從數(shù)據(jù)庫中根據(jù)key獲取數(shù)據(jù)的邏輯 private static String fetchDataFromDatabase(String key) { // 打印當(dāng)前正在加載的key,用于觀察加載行為 System.out.println("正在從數(shù)據(jù)庫加載數(shù)據(jù): " + key); try { Thread.sleep(500); // 模擬數(shù)據(jù)庫查詢的延遲,增加真實(shí)感 } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢復(fù)中斷狀態(tài) } // 返回模擬的數(shù)據(jù)庫查詢結(jié)果 return "Data for " + key; } }
3、Caffeine
Caffeine采用了W-TinyLFU(LUR和LFU的優(yōu)點(diǎn)結(jié)合)開源的緩存技術(shù)。緩存性能接近理論最優(yōu),屬于是Guava Cache的增強(qiáng)版。
Caffeine 是一個高性能的 Java 緩存庫,它改進(jìn)了 Guava Cache 的設(shè)計,具有以下特點(diǎn):
- 優(yōu)化的淘汰算法:采用 W-TinyLFU 算法,結(jié)合了 LRU 和 LFU 的優(yōu)點(diǎn)
- 卓越的性能:讀寫性能接近理論最優(yōu)值
- 異步支持:提供異步加載和刷新機(jī)制
- 豐富的特性:支持多種淘汰策略、權(quán)重計算、統(tǒng)計等功能
- 內(nèi)存友好:相比 Guava Cache 減少約 50% 的內(nèi)存占用
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.3</version> </dependency>
package com.helloworld.demo; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import java.util.concurrent.TimeUnit; public class CaffeineCacheTest { public static void main(String[] args) throws Exception { // 創(chuàng)建一個 Caffeine 緩存實(shí)例(注意:原注釋寫的是 Guava Cache,實(shí)際使用的是 Caffeine) Cache<String, String> loadingCache = Caffeine.newBuilder() .initialCapacity(5) // 設(shè)置初始緩存容量為 5 個條目 .maximumSize(10) // 設(shè)置緩存最大容量為 10 個條目,超過時將按照策略淘汰 .expireAfterWrite(17, TimeUnit.SECONDS) // 寫入后 17 秒過期 .expireAfterAccess(17, TimeUnit.SECONDS) // 最后一次訪問后 17 秒過期 .build(); // 構(gòu)建緩存實(shí)例 String key = "key"; // 定義緩存的鍵 loadingCache.put(key, "這是測試方法"); // 手動將鍵值對放入緩存 // 從緩存中獲取指定鍵的值 String value = loadingCache.getIfPresent(key); System.out.println(" 從緩存中獲取指定鍵(key)的值:" + value); // 輸出:這是測試方法 // 將指定的鍵從緩存中移除(使其失效) loadingCache.invalidate(key); value = loadingCache.getIfPresent(key); System.out.println(" 從緩存中獲取指定鍵(key)的值:" + value); // 輸出:null } }
4、Encache
Ehcache是一個純java的進(jìn)程內(nèi)緩存框架,具有快速、精干的特點(diǎn)。是hibernate默認(rèn)的cacheprovider。
- 優(yōu)點(diǎn):支持多種緩存淘汰算法,包括LFU,LRU和FIFO;緩存支持堆內(nèi)緩存,堆外緩存和磁盤緩存;支持多種集群方案,解決數(shù)據(jù)共享問題。
- 缺點(diǎn):性能比Caffeine差
<dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> <version>3.10.8</version> </dependency>
package com.helloworld.demo; 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 org.ehcache.config.units.MemoryUnit; /** * Ehcache 基礎(chǔ)使用示例類 */ public class EhcacheBasicExample { public static void main(String[] args) { // 1. 創(chuàng)建緩存管理器(CacheManager),它是管理所有緩存的核心對象 CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(); // 初始化緩存管理器,使其可以開始工作 cacheManager.init(); // 2. 創(chuàng)建一個緩存配置,定義緩存的鍵值類型和存儲策略 CacheConfigurationBuilder<String, String> config = CacheConfigurationBuilder .newCacheConfigurationBuilder( String.class, // 緩存鍵的類型為 String String.class, // 緩存值的類型為 String ResourcePoolsBuilder.heap(100) // 配置堆內(nèi)內(nèi)存緩存最多存儲 100 個條目 ); // 3. 根據(jù)配置創(chuàng)建一個名為 "myCache" 的緩存實(shí)例 Cache<String, String> myCache = cacheManager.createCache("myCache", config); // 4. 使用緩存:存儲和讀取數(shù)據(jù) myCache.put("key1", "value1"); // 往緩存中放入一個鍵值對 String value = myCache.get("key1"); // 從緩存中根據(jù) key 獲取對應(yīng)的 value System.out.println("獲取的值: " + value); // 打印獲取到的緩存值 // 5. 使用完緩存后,關(guān)閉緩存管理器以釋放資源 cacheManager.close(); } }
到此這篇關(guān)于Java實(shí)現(xiàn)本地緩存的四種方法實(shí)現(xiàn)與對比的文章就介紹到這了,更多相關(guān)Java本地緩存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis-generator生成多次重復(fù)代碼問題以及解決
在使用MySQL數(shù)據(jù)庫時,如果多個數(shù)據(jù)庫中存在相同表名,即使在URL中配置了數(shù)據(jù)庫名,也可能導(dǎo)致數(shù)據(jù)互相影響,解決這一問題的方法是在mapper-generator-config.xml文件中添加catalog屬性,明確指定逆向工程代碼所涉及表的數(shù)據(jù)庫名2024-10-10Java線程之join_動力節(jié)點(diǎn)Java學(xué)院整理
join() 定義在Thread.java中,下文通過源碼分享join(),需要的朋友參考下吧2017-05-05Java的Hibernate框架中集合類數(shù)據(jù)結(jié)構(gòu)的映射編寫教程
Hibernate可以將Java中幾個內(nèi)置的集合結(jié)構(gòu)映射為數(shù)據(jù)庫使用的關(guān)系模型,下面我們就來看一下Java的Hibernate框架中集合類數(shù)據(jù)結(jié)構(gòu)的映射編寫教程:2016-07-07Java數(shù)據(jù)結(jié)構(gòu)優(yōu)先隊(duì)列實(shí)練
通常都把隊(duì)列比喻成排隊(duì)買東西,大家都很守秩序,先排隊(duì)的人就先買東西。但是優(yōu)先隊(duì)列有所不同,它不遵循先進(jìn)先出的規(guī)則,而是根據(jù)隊(duì)列中元素的優(yōu)先權(quán),優(yōu)先權(quán)最大的先被取出,這篇文章主要介紹了java優(yōu)先隊(duì)列的真題,感興趣的朋友一起看看吧2022-07-07Java中防止數(shù)據(jù)重復(fù)提交超簡單的6種方法
在平時開發(fā)中,如果網(wǎng)速比較慢的情況下,用戶提交表單后,發(fā)現(xiàn)服務(wù)器半天都沒有響應(yīng),那么用戶可能會以為是自己沒有提交表單,就會再點(diǎn)擊提交按鈕重復(fù)提交表單,這篇文章主要給大家介紹了關(guān)于Java中防止數(shù)據(jù)重復(fù)提交超簡單的6種方法,需要的朋友可以參考下2021-11-11Java實(shí)現(xiàn)的數(shù)字簽名算法RSA完整示例
這篇文章主要介紹了Java實(shí)現(xiàn)的數(shù)字簽名算法RSA,結(jié)合完整實(shí)例形式詳細(xì)分析了RSA算法的相關(guān)概念、原理、實(shí)現(xiàn)方法及操作技巧,需要的朋友可以參考下2019-09-09