1、介紹
創(chuàng)新互聯(lián)秉承實(shí)現(xiàn)全網(wǎng)價(jià)值營(yíng)銷(xiāo)的理念,以專(zhuān)業(yè)定制企業(yè)官網(wǎng),成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì),小程序制作,網(wǎng)頁(yè)設(shè)計(jì)制作,手機(jī)網(wǎng)站制作,成都營(yíng)銷(xiāo)網(wǎng)站建設(shè)幫助傳統(tǒng)企業(yè)實(shí)現(xiàn)“互聯(lián)網(wǎng)+”轉(zhuǎn)型升級(jí)專(zhuān)業(yè)定制企業(yè)官網(wǎng),公司注重人才、技術(shù)和管理,匯聚了一批優(yōu)秀的互聯(lián)網(wǎng)技術(shù)人才,對(duì)客戶都以感恩的心態(tài)奉獻(xiàn)自己的專(zhuān)業(yè)和所長(zhǎng)。
在本文中,我們來(lái)看看Caffeine — 一個(gè)高性能的 Java 緩存庫(kù)。
緩存和 Map 之間的一個(gè)根本區(qū)別在于緩存可以回收存儲(chǔ)的 item。
回收策略為在指定時(shí)間刪除哪些對(duì)象。此策略直接影響緩存的命中率 — 緩存庫(kù)的一個(gè)重要特征。
Caffeine 因使用 Window TinyLfu 回收策略,提供了一個(gè)近乎最佳的命中率。
2、依賴
我們需要在 pom.xml 中添加 caffeine 依賴:
<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.5.5</version> </dependency>
您可以在Maven Central 上找到最新版本的 caffeine。
3、填充緩存
讓我們來(lái)了解一下 Caffeine 的三種緩存填充策略:手動(dòng)、同步加載和異步加載。
首先,我們?yōu)橐彺嬷写鎯?chǔ)的值類(lèi)型寫(xiě)一個(gè)類(lèi):
class DataObject { private final String data; private static int objectCounter = 0; // standard constructors/getters public static DataObject get(String data) { objectCounter++; return new DataObject(data); } }
3.1、手動(dòng)填充
在此策略中,我們手動(dòng)將值放入緩存之后再檢索。
讓我們初始化緩存:
Cache<String, DataObject> cache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.MINUTES) .maximumSize(100) .build();
現(xiàn)在,我們可以使用 getIfPresent 方法從緩存中獲取一些值。 如果緩存中不存在此值,則此方法將返回 null:
String key = "A"; DataObject dataObject = cache.getIfPresent(key); assertNull(dataObject);
我們可以使用 put 方法手動(dòng)填充緩存:
cache.put(key, dataObject); dataObject = cache.getIfPresent(key); assertNotNull(dataObject);
我們也可以使用 get 方法獲取值,該方法將一個(gè)參數(shù)為 key 的 Function 作為參數(shù)傳入。如果緩存中不存在該鍵,則該函數(shù)將用于提供回退值,該值在計(jì)算后插入緩存中:
dataObject = cache .get(key, k -> DataObject.get("Data for A")); assertNotNull(dataObject); assertEquals("Data for A", dataObject.getData());
get 方法可以原子方式執(zhí)行計(jì)算。這意味著您只進(jìn)行一次計(jì)算 — 即使多個(gè)線程同時(shí)請(qǐng)求該值。這就是為什么使用 get 優(yōu)于 getIfPresent。
有時(shí)我們需要手動(dòng)使一些緩存的值失效:
cache.invalidate(key); dataObject = cache.getIfPresent(key); assertNull(dataObject);
3.2、同步加載
這種加載緩存的方法使用了與用于初始化值的 Function 相似的手動(dòng)策略的 get 方法。讓我們看看如何使用它。
首先,我們需要初始化緩存:
LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(1, TimeUnit.MINUTES) .build(k -> DataObject.get("Data for " + k));
現(xiàn)在我們可以使用 get 方法檢索值:
DataObject dataObject = cache.get(key); assertNotNull(dataObject); assertEquals("Data for " + key, dataObject.getData());
我們也可以使用 getAll 方法獲取一組值:
Map<String, DataObject> dataObjectMap = cache.getAll(Arrays.asList("A", "B", "C")); assertEquals(3, dataObjectMap.size());
從傳遞給 build 方法的底層后端初始化函數(shù)檢索值。 這使得可以使用緩存作為訪問(wèn)值的主要門(mén)面(Facade)。
3.3、異步加載
此策略的作用與之前相同,但是以異步方式執(zhí)行操作,并返回一個(gè)包含值的 CompletableFuture:
AsyncLoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(1, TimeUnit.MINUTES) .buildAsync(k -> DataObject.get("Data for " + k));
我們可以以相同的方式使用 get 和 getAll 方法,同時(shí)考慮到他們返回的是 CompletableFuture:
String key = "A"; cache.get(key).thenAccept(dataObject -> { assertNotNull(dataObject); assertEquals("Data for " + key, dataObject.getData()); }); cache.getAll(Arrays.asList("A", "B", "C")) .thenAccept(dataObjectMap -> assertEquals(3, dataObjectMap.size()));
CompletableFuture 有許多有用的 API,您可以在此文中獲取更多內(nèi)容。
4、值回收
Caffeine 有三個(gè)值回收策略:基于大小,基于時(shí)間和參考。
4.1、基于大小回收
這種回收方式假定當(dāng)超過(guò)配置的緩存大小限制時(shí)會(huì)發(fā)生回收。 獲取大小有兩種方法:緩存中計(jì)數(shù)對(duì)象,或獲取權(quán)重。
讓我們看看如何計(jì)算緩存中的對(duì)象。當(dāng)緩存初始化時(shí),其大小等于零:
LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumSize(1) .build(k -> DataObject.get("Data for " + k)); assertEquals(0, cache.estimatedSize());
當(dāng)我們添加一個(gè)值時(shí),大小明顯增加:
cache.get("A"); assertEquals(1, cache.estimatedSize());
我們可以將第二個(gè)值添加到緩存中,這導(dǎo)致第一個(gè)值被刪除:
cache.get("B"); cache.cleanUp(); assertEquals(1, cache.estimatedSize());
值得一提的是,在獲取緩存大小之前,我們調(diào)用了 cleanUp 方法。 這是因?yàn)榫彺婊厥毡划惒綀?zhí)行,這種方法有助于等待回收的完成。
我們還可以傳遞一個(gè) weigher Function 來(lái)獲取緩存的大小:
LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumWeight(10) .weigher((k,v) -> 5) .build(k -> DataObject.get("Data for " + k)); assertEquals(0, cache.estimatedSize()); cache.get("A"); assertEquals(1, cache.estimatedSize()); cache.get("B"); assertEquals(2, cache.estimatedSize());
當(dāng) weight 超過(guò) 10 時(shí),值將從緩存中刪除:
cache.get("C"); cache.cleanUp(); assertEquals(2, cache.estimatedSize());
4.2、基于時(shí)間回收
這種回收策略是基于條目的到期時(shí)間,有三種類(lèi)型:
讓我們使用 expireAfterAccess 方法配置訪問(wèn)后過(guò)期策略:
LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) .build(k -> DataObject.get("Data for " + k));
要配置寫(xiě)入后到期策略,我們使用 expireAfterWrite 方法:
cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.SECONDS) .weakKeys() .weakValues() .build(k -> DataObject.get("Data for " + k));
要初始化自定義策略,我們需要實(shí)現(xiàn) Expiry 接口:
cache = Caffeine.newBuilder().expireAfter(new Expiry<String, DataObject>() { @Override public long expireAfterCreate( String key, DataObject value, long currentTime) { return value.getData().length() * 1000; } @Override public long expireAfterUpdate( String key, DataObject value, long currentTime, long currentDuration) { return currentDuration; } @Override public long expireAfterRead( String key, DataObject value, long currentTime, long currentDuration) { return currentDuration; } }).build(k -> DataObject.get("Data for " + k));
4.3、基于引用回收
我們可以將緩存配置為啟用緩存鍵值的垃圾回收。為此,我們將 key 和 value 配置為 弱引用,并且我們可以僅配置軟引用以進(jìn)行垃圾回收。
當(dāng)沒(méi)有任何對(duì)對(duì)象的強(qiáng)引用時(shí),使用 WeakRefence 可以啟用對(duì)象的垃圾收回收。SoftReference 允許對(duì)象根據(jù) JVM 的全局最近最少使用(Least-Recently-Used)的策略進(jìn)行垃圾回收。有關(guān) Java 引用的更多詳細(xì)信息,請(qǐng)參見(jiàn)此處。
我們應(yīng)該使用 Caffeine.weakKeys()、Caffeine.weakValues() 和 Caffeine.softValues() 來(lái)啟用每個(gè)選項(xiàng):
LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.SECONDS) .weakKeys() .weakValues() .build(k -> DataObject.get("Data for " + k)); cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.SECONDS) .softValues() .build(k -> DataObject.get("Data for " + k));
5、刷新
可以將緩存配置為在定義的時(shí)間段后自動(dòng)刷新條目。讓我們看看如何使用 refreshAfterWrite 方法:
Caffeine.newBuilder() .refreshAfterWrite(1, TimeUnit.MINUTES) .build(k -> DataObject.get("Data for " + k));
這里我們應(yīng)該要明白 expireAfter 和 refreshAfter 之間的區(qū)別。 當(dāng)請(qǐng)求過(guò)期條目時(shí),執(zhí)行將發(fā)生阻塞,直到 build Function 計(jì)算出新值為止。
但是,如果條目可以刷新,則緩存將返回一個(gè)舊值,并異步重新加載該值。
6、統(tǒng)計(jì)
Caffeine 有一種記錄緩存使用情況的統(tǒng)計(jì)方式:
LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumSize(100) .recordStats() .build(k -> DataObject.get("Data for " + k)); cache.get("A"); cache.get("A"); assertEquals(1, cache.stats().hitCount()); assertEquals(1, cache.stats().missCount());
我們也可能會(huì)傳入 recordStats supplier,創(chuàng)建一個(gè) StatsCounter 的實(shí)現(xiàn)。每次與統(tǒng)計(jì)相關(guān)的更改將推送此對(duì)象。
7、結(jié)論
在本文中,我們熟悉了 Java 的 Caffeine 緩存庫(kù)。 我們看到了如何配置和填充緩存,以及如何根據(jù)我們的需要選擇適當(dāng)?shù)牡狡诨蛩⑿虏呗浴?/p>
文中示例的源代碼可以在 Github 上找到。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
標(biāo)題名稱:詳細(xì)介紹高性能Java緩存庫(kù)Caffeine
本文網(wǎng)址:http://jinyejixie.com/article46/jjhphg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站策劃、響應(yīng)式網(wǎng)站、App開(kāi)發(fā)、Google、營(yíng)銷(xiāo)型網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)