摘要:并且添加了監聽器,當數據被刪除后會打印日志。六總結回顧緩存加載顯示插入緩存回收,定時,,軟弱引用,顯示刪除接口方法,監聽器清理緩存時間只有在獲取數據時才或清理緩存,使用者可以單起線程采用方法主動清理。
摘要: 學習Google內部使用的工具包Guava,在Java項目中輕松地增加緩存,提高程序獲取數據的效率。
一、什么是緩存?
根據科普中國的定義,緩存就是數據交換的緩沖區(稱作Cache),當某一硬件要讀取數據時,會首先從緩存中查找需要的數據,如果找到了則直接執行,找不到的話則從內存中找。由于緩存的運行速度比內存快得多,故緩存的作用就是幫助硬件更快地運行。
在這里,我們借用了硬件緩存的概念,當在Java程序中計算或查詢數據的代價很高,并且對同樣的計算或查詢條件需要不止一次獲取數據的時候,就應當考慮使用緩存。換句話說,緩存就是以空間換時間,大部分應用在各種IO,數據庫查詢等耗時較長的應用當中。
二、緩存原理
當獲取數據時,程序將先從一個存儲在內存中的數據結構中獲取數據。如果數據不存在,則在磁盤或者數據庫中獲取數據并存入到數據結構當中。之后程序需要再次獲取數據時,則會先查詢這個數據結構。從內存中獲取數據時間明顯小于通過IO獲取數據,這個數據結構就是緩存的實現。
這里引入一個概念,緩存命中率:從緩存中獲取到數據的次數/全部查詢次數,命中率越高說明這個緩存的效率好。由于機器內存的限制,緩存一般只能占據有限的內存大小,緩存需要不定期的刪除一部分數據,從而保證不會占據大量內存導致機器崩潰。
如何提高命中率呢?那就得從刪除一部分數據著手了。目前有三種刪除數據的方式,分別是:FIFO(先進先出)、LFU(定期淘汰最少使用次數)、LRU(淘汰最長時間未被使用)。
三、GuavaCache工作方式
GuavaCache的工作流程:獲取數據->如果存在,返回數據->計算獲取數據->存儲返回。由于特定的工作流程,使用者必須在創建Cache或者獲取數據時指定不存在數據時應當怎么獲取數據。GuavaCache采用LRU的工作原理,使用者必須指定緩存數據的大小,當超過緩存大小時,必定引發數據刪除。GuavaCache還可以讓用戶指定緩存數據的過期時間,刷新時間等等很多有用的功能。
四、GuavaCache使用Demo
4.1 簡單使用
有人說我就想簡簡單單的使用cache,就像Map那樣方便就行。接下來展示一段簡單的使用方式。
首先定義一個需要存儲的Bean,對象Man:
/**
@author jiangmitiao
@version V1.0
@Title: 標題
@Description: Bean
@date 2016/10/27 10:01
*/
public class Man {
//身份證號 private String id; //姓名 private String name; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Man{" + "id="" + id + """ + ", name="" + name + """ + "}"; }
}
接下來我們寫一個Demo:
import com.google.common.cache.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
/**
@author jiangmitiao
@version V1.0
@Description: Demo
@date 2016/10/27 10:00
*/
public class GuavaCachDemo {
private LoadingCacheloadingCache; //loadingCache public void InitLoadingCache() { //指定一個如果數據不存在獲取數據的方法 CacheLoader cacheLoader = new CacheLoader () { @Override public Man load(String key) throws Exception { //模擬mysql操作 Logger logger = LoggerFactory.getLogger("LoadingCache"); logger.info("LoadingCache測試 從mysql加載緩存ing...(2s)"); Thread.sleep(2000); logger.info("LoadingCache測試 從mysql加載緩存成功"); Man tmpman = new Man(); tmpman.setId(key); tmpman.setName("其他人"); if (key.equals("001")) { tmpman.setName("張三"); return tmpman; } if (key.equals("002")) { tmpman.setName("李四"); return tmpman; } return tmpman; } }; //緩存數量為1,為了展示緩存刪除效果 loadingCache = CacheBuilder.newBuilder().maximumSize(1).build(cacheLoader); } //獲取數據,如果不存在返回null public Man getIfPresentloadingCache(String key){ return loadingCache.getIfPresent(key); } //獲取數據,如果數據不存在則通過cacheLoader獲取數據,緩存并返回 public Man getCacheKeyloadingCache(String key){ try { return loadingCache.get(key); } catch (ExecutionException e) { e.printStackTrace(); } return null; } //直接向緩存put數據 public void putloadingCache(String key,Man value){ Logger logger = LoggerFactory.getLogger("LoadingCache"); logger.info("put key :{} value : {}",key,value.getName()); loadingCache.put(key,value); }
}
接下來,我們寫一些測試方法,檢測一下
public class Test {
public static void main(String[] args){ GuavaCachDemo cachDemo = new GuavaCachDemo() System.out.println("使用loadingCache"); cachDemo.InitLoadingCache(); System.out.println("使用loadingCache get方法 第一次加載"); Man man = cachDemo.getCacheKeyloadingCache("001"); System.out.println(man); System.out.println(" 使用loadingCache getIfPresent方法 第一次加載"); man = cachDemo.getIfPresentloadingCache("002"); System.out.println(man); System.out.println(" 使用loadingCache get方法 第一次加載"); man = cachDemo.getCacheKeyloadingCache("002"); System.out.println(man); System.out.println(" 使用loadingCache get方法 已加載過"); man = cachDemo.getCacheKeyloadingCache("002"); System.out.println(man); System.out.println(" 使用loadingCache get方法 已加載過,但是已經被剔除掉,驗證重新加載"); man = cachDemo.getCacheKeyloadingCache("001"); System.out.println(man); System.out.println(" 使用loadingCache getIfPresent方法 已加載過"); man = cachDemo.getIfPresentloadingCache("001"); System.out.println(man); System.out.println(" 使用loadingCache put方法 再次get"); Man newMan = new Man(); newMan.setId("001"); newMan.setName("額外添加"); cachDemo.putloadingCache("001",newMan); man = cachDemo.getCacheKeyloadingCache("001"); System.out.println(man); }
}
測試結果如下:
150850_81Jv_1983603.png
4.2 高級特性
由于目前使用有局限性,接下來只講我用到的一些方法。
我來演示一下GuavaCache自帶的兩個Cache
GuavaCacheDemo.java
import com.google.common.cache.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
/**
@author jiangmitiao
@version V1.0
@Description: Demo
@date 2016/10/27 10:00
*/
public class GuavaCachDemo {
private Cachecache; private LoadingCache loadingCache; private RemovalListener removalListener; public void Init(){ //移除key-value監聽器 removalListener = new RemovalListener (){ public void onRemoval(RemovalNotification notification) { Logger logger = LoggerFactory.getLogger("RemovalListener"); logger.info(notification.getKey()+"被移除"); //可以在監聽器中獲取key,value,和刪除原因 notification.getValue(); notification.getCause();//EXPLICIT、REPLACED、COLLECTED、EXPIRED、SIZE }}; //可以使用RemovalListeners.asynchronous方法將移除監聽器設為異步方法 //removalListener = RemovalListeners.asynchronous(removalListener, new ThreadPoolExecutor(1,1,1000, TimeUnit.MINUTES,new ArrayBlockingQueue (1))); } //loadingCache public void InitLoadingCache() { //指定一個如果數據不存在獲取數據的方法 CacheLoader cacheLoader = new CacheLoader () { @Override public Man load(String key) throws Exception { //模擬mysql操作 Logger logger = LoggerFactory.getLogger("LoadingCache"); logger.info("LoadingCache測試 從mysql加載緩存ing...(2s)"); Thread.sleep(2000); logger.info("LoadingCache測試 從mysql加載緩存成功"); Man tmpman = new Man(); tmpman.setId(key); tmpman.setName("其他人"); if (key.equals("001")) { tmpman.setName("張三"); return tmpman; } if (key.equals("002")) { tmpman.setName("李四"); return tmpman; } return tmpman; } }; //緩存數量為1,為了展示緩存刪除效果 loadingCache = CacheBuilder.newBuilder(). //設置2分鐘沒有獲取將會移除數據 expireAfterAccess(2, TimeUnit.MINUTES). //設置2分鐘沒有更新數據則會移除數據 expireAfterWrite(2, TimeUnit.MINUTES). //每1分鐘刷新數據 refreshAfterWrite(1,TimeUnit.MINUTES). //設置key為弱引用 weakKeys().
// weakValues().//設置存在時間和刷新時間后不能再次設置
// softValues().//設置存在時間和刷新時間后不能再次設置
maximumSize(1). removalListener(removalListener). build(cacheLoader); } //獲取數據,如果不存在返回null public Man getIfPresentloadingCache(String key){ return loadingCache.getIfPresent(key); } //獲取數據,如果數據不存在則通過cacheLoader獲取數據,緩存并返回 public Man getCacheKeyloadingCache(String key){ try { return loadingCache.get(key); } catch (ExecutionException e) { e.printStackTrace(); } return null; } //直接向緩存put數據 public void putloadingCache(String key,Man value){ Logger logger = LoggerFactory.getLogger("LoadingCache"); logger.info("put key :{} value : {}",key,value.getName()); loadingCache.put(key,value); } public void InitDefault() { cache = CacheBuilder.newBuilder(). expireAfterAccess(2, TimeUnit.MINUTES). expireAfterWrite(2, TimeUnit.MINUTES).
// refreshAfterWrite(1,TimeUnit.MINUTES).//沒有cacheLoader的cache不能設置刷新,因為沒有指定獲取數據的方式
weakKeys().
// weakValues().//設置存在時間和刷新時間后不能再次設置
// softValues().//設置存在時間和刷新時間后不能再次設置
maximumSize(1). removalListener(removalListener). build(); } public Man getIfPresentCache(String key){ return cache.getIfPresent(key); } public Man getCacheKeyCache(final String key) throws ExecutionException { return cache.get(key, new Callable() { public Man call() throws Exception { //模擬mysql操作 Logger logger = LoggerFactory.getLogger("Cache"); logger.info("Cache測試 從mysql加載緩存ing...(2s)"); Thread.sleep(2000); logger.info("Cache測試 從mysql加載緩存成功"); Man tmpman = new Man(); tmpman.setId(key); tmpman.setName("其他人"); if (key.equals("001")) { tmpman.setName("張三"); return tmpman; } if (key.equals("002")) { tmpman.setName("李四"); return tmpman; } return tmpman; } }); } public void putCache(String key,Man value){ Logger logger = LoggerFactory.getLogger("Cache"); logger.info("put key :{} value : {}",key,value.getName()); cache.put(key,value); }
}
在這個demo中,分別采用了Guava自帶的兩個Cache:LocalLoadingCache和LocalManualCache。并且添加了監聽器,當數據被刪除后會打印日志。
Main:
public static void main(String[] args){
GuavaCachDemo cachDemo = new GuavaCachDemo(); cachDemo.Init(); System.out.println("使用loadingCache"); cachDemo.InitLoadingCache(); System.out.println("使用loadingCache get方法 第一次加載"); Man man = cachDemo.getCacheKeyloadingCache("001"); System.out.println(man); System.out.println(" 使用loadingCache getIfPresent方法 第一次加載"); man = cachDemo.getIfPresentloadingCache("002"); System.out.println(man); System.out.println(" 使用loadingCache get方法 第一次加載"); man = cachDemo.getCacheKeyloadingCache("002"); System.out.println(man); System.out.println(" 使用loadingCache get方法 已加載過"); man = cachDemo.getCacheKeyloadingCache("002"); System.out.println(man); System.out.println(" 使用loadingCache get方法 已加載過,但是已經被剔除掉,驗證重新加載"); man = cachDemo.getCacheKeyloadingCache("001"); System.out.println(man); System.out.println(" 使用loadingCache getIfPresent方法 已加載過"); man = cachDemo.getIfPresentloadingCache("001"); System.out.println(man); System.out.println(" 使用loadingCache put方法 再次get"); Man newMan = new Man(); newMan.setId("001"); newMan.setName("額外添加"); cachDemo.putloadingCache("001",newMan); man = cachDemo.getCacheKeyloadingCache("001"); System.out.println(man); /////////////////////////////////// System.out.println(" 使用Cache"); cachDemo.InitDefault(); System.out.println("使用Cache get方法 第一次加載"); try { man = cachDemo.getCacheKeyCache("001"); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(man); System.out.println(" 使用Cache getIfPresent方法 第一次加載"); man = cachDemo.getIfPresentCache("002"); System.out.println(man); System.out.println(" 使用Cache get方法 第一次加載"); try { man = cachDemo.getCacheKeyCache("002"); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(man); System.out.println(" 使用Cache get方法 已加載過"); try { man = cachDemo.getCacheKeyCache("002"); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(man); System.out.println(" 使用Cache get方法 已加載過,但是已經被剔除掉,驗證重新加載"); try { man = cachDemo.getCacheKeyCache("001"); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(man); System.out.println(" 使用Cache getIfPresent方法 已加載過"); man = cachDemo.getIfPresentCache("001"); System.out.println(man); System.out.println(" 使用Cache put方法 再次get"); Man newMan1 = new Man(); newMan1.setId("001"); newMan1.setName("額外添加"); cachDemo.putloadingCache("001",newMan1); man = cachDemo.getCacheKeyloadingCache("001"); System.out.println(man);
}
測試結果如下:
152412_Afd2_1983603.png
152425_uKCJ_1983603.png
由上述結果可以表明,GuavaCache可以在數據存儲到達指定大小后刪除數據結構中的數據。我們可以設置定期刪除而達到定期從數據庫、磁盤等其他地方更新數據等(再次訪問時數據不存在重新獲?。R部梢圆捎枚〞r刷新的方式更新數據。
還可以設置移除監聽器對被刪除的數據進行一些操作。通過RemovalListeners.asynchronous(RemovalListener,Executor)方法將監聽器設為異步,筆者通過實驗發現,異步監聽不會在刪除數據時立刻調用監聽器方法。
五、GuavaCache結構初探
153356_Z1zV_1983603.png
類結構圖
GuavaCache并不希望我們設置復雜的參數,而讓我們采用建造者模式創建Cache。GuavaCache分為兩種Cache:Cache,LoadingCache。LoadingCache繼承了Cache,他比Cache主要多了get和refresh方法。多這兩個方法能干什么呢?
在第四節高級特性demo中,我們看到builder生成不帶CacheLoader的Cache實例。在類結構圖中其實是生成了LocalManualCache類實例。而帶CacheLoader的Cache實例生成的是LocalLoadingCache。他可以定時刷新數據,因為獲取數據的方法已經作為構造參數方法存入了Cache實例中。同樣,在get時,不需要像LocalManualCache還需要傳入一個Callable實例。
實際上,這兩個Cache實現類都繼承自LocalCache,大部分實現都是父類做的。
六、總結回顧
緩存加載:CacheLoader、Callable、顯示插入(put)
緩存回收:LRU,定時(expireAfterAccess,expireAfterWrite),軟弱引用,顯示刪除(Cache接口方法invalidate,invalidateAll)
監聽器:CacheBuilder.removalListener(RemovalListener)
清理緩存時間:只有在獲取數據時才或清理緩存LRU,使用者可以單起線程采用Cache.cleanUp()方法主動清理。
刷新:主動刷新方法LoadingCache.referesh(K)
信息統計:CacheBuilder.recordStats() 開啟Guava Cache的統計功能。Cache.stats() 返回CacheStats對象。(其中包括命中率等相關信息)
獲取當前緩存所有數據:cache.asMap(),cache.asMap().get(Object)會刷新數據的訪問時間(影響的是:創建時設置的在多久沒訪問后刪除數據)
LocalManualCache和LocalLoadingCache的選擇
ManualCache可以在get時動態設置獲取數據的方法,而LoadingCache可以定時刷新數據。如何取舍?我認為在緩存數據有很多種類的時候采用第一種cache。而數據單一,數據庫數據會定時刷新時采用第二種cache。
參考資料:
http://www.cnblogs.com/peida/...
https://github.com/tiantianga...
http://www.blogjava.net/DLevi...
http://ifeve.com/google-guava...
更多文章:http://blog.gavinzh.com
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76452.html
摘要:并且添加了監聽器,當數據被刪除后會打印日志。六總結回顧緩存加載顯示插入緩存回收,定時,,軟弱引用,顯示刪除接口方法,監聽器清理緩存時間只有在獲取數據時才或清理緩存,使用者可以單起線程采用方法主動清理。 摘要: 學習Google內部使用的工具包Guava,在Java項目中輕松地增加緩存,提高程序獲取數據的效率。 一、什么是緩存? 根據科普中國的定義,緩存就是數據交換的緩沖區(稱作Cach...
摘要:前言在上文源碼分析原理中分析了的相關原理。我在北京模擬執行你在哪兒回復最后執行結果開始提問提問完畢,我去干其他事了收到消息你在哪兒等待響應中。。。。?;貜臀以诒本┻@樣一個模擬的異步事件回調就完成了。 showImg(https://segmentfault.com/img/remote/1460000015643387?w=2048&h=1150); 前言 在上文「Guava 源碼分析...
摘要:前言在上文源碼分析原理中分析了的相關原理。我在北京模擬執行你在哪兒回復最后執行結果開始提問提問完畢,我去干其他事了收到消息你在哪兒等待響應中。。。。?;貜臀以诒本┻@樣一個模擬的異步事件回調就完成了。 showImg(https://segmentfault.com/img/remote/1460000015643387?w=2048&h=1150); 前言 在上文「Guava 源碼分析...
摘要:緩存總體可分為兩種集中式緩存和分布式緩存集中式緩存與分布式緩存的區別其實就在于集中與非集中的概念,其對象可能是服務器內存條硬盤等。內存條版本緩存集中在一臺服務器的一條內存條上,為集中式緩存。 背景 緩存的主要作用是暫時在內存中保存業務系統的數據處理結果,并且等待下次訪問使用。在日長開發有很多場合,有一些數據量不是很大,不會經常改動,并且訪問非常頻繁。但是由于受限于硬盤IO的性能或者遠程...
摘要:緩存總體可分為兩種集中式緩存和分布式緩存集中式緩存與分布式緩存的區別其實就在于集中與非集中的概念,其對象可能是服務器內存條硬盤等。內存條版本緩存集中在一臺服務器的一條內存條上,為集中式緩存。 背景 緩存的主要作用是暫時在內存中保存業務系統的數據處理結果,并且等待下次訪問使用。在日長開發有很多場合,有一些數據量不是很大,不會經常改動,并且訪問非常頻繁。但是由于受限于硬盤IO的性能或者遠程...
閱讀 2422·2021-11-11 11:01
閱讀 3296·2021-10-11 10:57
閱讀 2656·2021-09-30 09:46
閱讀 3497·2021-07-26 23:38
閱讀 1573·2019-08-29 12:22
閱讀 655·2019-08-29 11:28
閱讀 2359·2019-08-26 14:04
閱讀 3059·2019-08-23 18:34