Caffeine Weigher

Weigher 接口

Weigher?是 Caffeine 緩存庫中一個非常重要的函數式接口,它用于計算緩存中每個條目(entry)的權重(weight)。這個權重值主要用于基于容量的驅逐策略,特別是當你希望緩存的總大小不是基于條目數量,而是基于條目占用的“成本”(例如內存大小)時。

Weigher?是一個泛型接口,接收鍵(K)和值(V)的類型作為參數。

// ... existing code ...
@NullMarked
@FunctionalInterface
public interface Weigher<K, V> {/*** Returns the weight of a cache entry. There is no unit for entry weights; rather they are simply* relative to each other.** @param key the key to weigh* @param value the value to weigh* @return the weight of the entry; must be non-negative*/int weigh(K key, V value);// ... existing code ...
}

語義分析:

  1. @FunctionalInterface: 這表明?Weigher?是一個函數式接口,它只包含一個抽象方法?weigh。這意味著可以使用 Lambda 表達式來方便地創建它的實例。
  2. int weigh(K key, V value): 這是接口的核心方法。
    • 它接收一個緩存條目的?key?和?value?作為輸入。
    • 它返回一個?int?類型的權重值。
    • 關鍵語義:
      • 權重無單位: Javadoc 中明確指出,權重沒有固定的單位(比如字節),它只是一個相對值。Caffeine 使用這些相對值來計算緩存的總權重。
      • 非負性: 返回的權重值必須是非負的?(>= 0)。如果返回負值,會導致未定義的行為,后續我們會看到 Caffeine 如何通過?boundedWeigher?來保證這一點。
      • 使用場景: 當你使用?Caffeine.newBuilder().maximumWeight(long)?來配置緩存時,就必須提供一個?Weigher?實現。Caffeine 會累加所有條目的權重,當總權重超過?maximumWeight?時,就會觸發驅逐策略。

Weigher?接口內部定義了兩個靜態工廠方法和兩個內部實現類,以提供常用功能和增強安全性。

singletonWeigher()

// ... existing code .../*** Returns a weigher where an entry has a weight of {@code 1}.** @param <K> the type of keys* @param <V> the type of values* @return a weigher where an entry has a weight of {@code 1}*/static <K, V> Weigher<K, V> singletonWeigher() {@SuppressWarnings("unchecked")var instance = (Weigher<K, V>) SingletonWeigher.INSTANCE;return instance;}
// ... existing code ...

語義分析:

  • 此方法返回一個默認的?Weigher?實現,該實現為每個緩存條目都返回權重?1
  • 這實際上等價于基于條目數量的驅逐策略,即?maximumWeight(N)?和?maximumSize(N)?在這種情況下效果相同。
  • 它通過內部的?SingletonWeigher?enum 實現,這是一種高效且線程安全的單例模式。

boundedWeigher(Weigher<K, V> delegate)

// ... existing code .../*** Returns a weigher that enforces that the weight is non-negative.** @param delegate the weigher to weighs the entry* @param <K> the type of keys* @param <V> the type of values* @return a weigher that enforces that the weight is non-negative*/static <K, V> Weigher<K, V> boundedWeigher(Weigher<K, V> delegate) {return new BoundedWeigher<>(delegate);}
}

語義分析:

  • 這是一個裝飾器(Decorator)方法。它接收一個用戶提供的?Weigher?(delegate),并返回一個新的?Weigher?實例。
  • 這個新的實例會調用用戶提供的?Weigher?來計算權重,但會額外增加一個檢查:確保返回的權重值是非負的。如果用戶實現返回了負數,BoundedWeigher?會拋出?IllegalArgumentException,從而保證了權重的合法性。
  • Caffeine 內部在構建緩存時,會使用此方法來包裝用戶提供的?Weigher,以增加健壯性。

內部實現?SingletonWeigher?和?BoundedWeigher

// ... existing code ...
enum SingletonWeigher implements Weigher<Object, Object> {INSTANCE;@Override public int weigh(Object key, Object value) {return 1;}
}final class BoundedWeigher<K, V> implements Weigher<K, V>, Serializable {private static final long serialVersionUID = 1;@SuppressWarnings("serial")final Weigher<? super K, ? super V> delegate;BoundedWeigher(Weigher<? super K, ? super V> delegate) {this.delegate = requireNonNull(delegate);}@Overridepublic int weigh(K key, V value) {int weight = delegate.weigh(key, value);requireArgument(weight >= 0);return weight;}Object writeReplace() {return delegate;}
}

語義分析:

  • SingletonWeigher:
    • 使用?enum?實現單例,簡潔、高效、線程安全。
    • weigh?方法簡單地返回?1
  • BoundedWeigher<K, V>:
    • 這是一個 final 類,實現了?Weigher?和?Serializable?接口。
    • 構造函數接收一個?delegate?(用戶的?Weigher?實現)。
    • weigh?方法先調用?delegate.weigh,然后使用?requireArgument(weight >= 0)?檢查結果。
    • writeReplace()?方法是一個序列化技巧。當?BoundedWeigher?實例被序列化時,實際被寫入流的是其內部的?delegate?對象。這可以防止序列化不必要的包裝層。

BoundedWeigher 的名字意味著它為一個 Weigher 提供了邊界檢查的功能。它通過包裝(裝飾)另一個 Weigher,強制執行了 Weigher 接口文檔中“權重必須為非負數”的契約,為程序增加了健壯性。

Java 枚舉的結構

表面上看,enum?是一個特殊的關鍵字,但實際上,它只是一個語法糖。當 Java 編譯器遇到?enum?定義時,它會做幾件關鍵的事情:

  1. 創建一個?final?類:每個枚舉類型都會被編譯成一個?final?的類,這個類隱式地繼承自?java.lang.Enum。【所以自己實現的枚舉類不能再繼承】
  2. 創建?public static final?實例:枚舉中聲明的每一個常量(在?SingletonWeigher?中就是?INSTANCE)都會成為這個?final?類的一個?public static final?字段。這個字段的類型就是該枚舉類本身。
  3. 私有構造函數:枚舉的構造函數是隱式?private?的。你不能在外部通過?new?來創建枚舉的實例。
  4. JVM 保證單例:JVM 在類加載的初始化階段,會執行一個靜態代碼塊(<clinit>),在這個階段,它會調用私有構造函數來創建并初始化所有枚舉常量實例。這個過程由 JVM 保證是線程安全的,并且每個枚舉常量在整個 JVM 生命周期中只會被實例化一次。

總結

Weigher?接口為 Caffeine 提供了一種靈活的、基于權重的容量驅逐機制。開發者可以通過實現這個接口,根據業務需求自定義緩存條目的“成本”(例如,一個圖片對象的權重可以是其占用的字節數,一個列表的權重可以是其元素個數)。接口本身的設計通過靜態方法和內部類提供了默認實現和安全保障,使得該功能既強大又易于使用。

Async內部類AsyncWeigher?

AsyncWeigher?是?Async?工具類中的一個靜態內部類。它的核心目的是為異步緩存中的條目計算權重

在?AsyncCache?中,緩存的值(Value)是一個?CompletableFuture<V>,而不是直接的?V。這意味著當你向緩存中放入一個條目時,實際的值可能還在計算中,尚未完成。這就帶來一個問題:如果我們要根據值的大小來計算權重,那么在一個?CompletableFuture?還未完成時,我們是無法知道最終值的權重的。

AsyncWeigher?就是為了解決這個問題而設計的。它充當了一個適配器(Adapter)或者說裝飾器(Decorator),將一個普通的?Weigher<K, V>(計算最終值的權重)包裝成一個?Weigher<K, CompletableFuture<V>>(計算?Future?值的權重)。

我們來看一下它的代碼結構:

// ... existing code .../*** A weigher for asynchronous computations. When the value is being loaded this weigher returns* {@code 0} to indicate that the entry should not be evicted due to a size constraint. If the* value is computed successfully then the entry must be reinserted so that the weight is updated* and the expiration timeouts reflect the value once present. This can be done safely using* {@link java.util.Map#replace(Object, Object, Object)}.*/static final class AsyncWeigher<K, V> implements Weigher<K, CompletableFuture<V>>, Serializable {private static final long serialVersionUID = 1L;final Weigher<K, V> delegate;AsyncWeigher(Weigher<K, V> delegate) {this.delegate = requireNonNull(delegate);}@Overridepublic int weigh(K key, CompletableFuture<V> future) {return isReady(future) ? delegate.weigh(key, future.join()) : 0;}Object writeReplace() {return delegate;}}static boolean isReady(@Nullable CompletableFuture<?> future) {return (future != null) && future.isDone() && !future.isCompletedExceptionally();}
// ... existing code ...
  • implements Weigher<K, CompletableFuture<V>>, Serializable:

    • 它實現了?Weigher?接口,但請注意泛型類型:鍵是?K,而值是?CompletableFuture<V>。這表明它的?weigh?方法接收的是一個?Future?對象。
    • 實現?Serializable?接口是為了讓包含它的緩存實例可以被序列化。
  • final Weigher<K, V> delegate;:

    • 這是一個關鍵字段,它持有一個“真正”的?Weigher?實例,這個實例知道如何根據最終的?V?類型的值來計算權重。AsyncWeigher?的工作就是委托給它。
  • AsyncWeigher(Weigher<K, V> delegate):

    • 構造函數接收一個用戶定義的?Weigher<K, V>,并保存到?delegate?字段。

?核心邏輯:weigh?方法

weigh?方法是?AsyncWeigher?的核心所在。

// ... existing code ...@Overridepublic int weigh(K key, CompletableFuture<V> future) {return isReady(future) ? delegate.weigh(key, future.join()) : 0;}
// ... existing code ...

這里的邏輯非常清晰,可以分為兩種情況:

  1. 當?CompletableFuture?已經就緒 (isReady):

    • isReady(future)?是?Async?類中的一個輔助方法,它檢查?future?是否已正常完成并且結果不為?null
    • 如果?future?已經就緒,代碼會調用?future.join()?來獲取最終的計算結果(類型為?V)。
    • 然后,它調用?delegate.weigh(key, future.join()),即使用用戶提供的原始?Weigher?來計算這個真實值的權重,并返回該權重。
  2. 當?CompletableFuture?尚未就緒:

    • 如果?future?仍在計算中、計算失敗或結果為?nullisReady(future)?會返回?false
    • 在這種情況下,weigh?方法直接返回?0

這個設計的精妙之處在于

  • 保護未完成的條目:對于正在加載的緩存條目,其權重為?0。這意味著在基于權重的驅逐策略下,這個條目幾乎不會因為容量問題被驅逐。這給了異步任務足夠的時間去完成,避免了“剛開始加載就被驅逐”的尷尬情況。
  • 延遲權重計算:它將權重的實際計算推遲到值真正可用時。

Javadoc 中的重要提示

AsyncWeigher?的 Javadoc 包含一段非常重要的說明:

If the value is computed successfully then the entry must be reinserted so that the weight is updated and the expiration timeouts reflect the value once present. This can be done safely using {@link java.util.Map#replace(Object, Object, Object)}.

中文解釋:當?CompletableFuture?成功計算出值后,這個條目 必須被重新插入(reinserted) 到緩存中。

為什么需要這樣做?

因為當?Future?完成后,它的權重從?0?變成了一個實際的值。但緩存系統不會自動重新計算已有條目的權重。因此,需要手動觸發一次更新操作(例如?cache.put(key, future)?或?cache.asMap().replace(key, future, future)),這次操作會再次調用?AsyncWeigher.weigh?方法。此時,由于?future?已經就緒,weigh?方法會返回真實的權重,緩存的總權重也隨之更新,驅逐策略便能正確工作。

Caffeine 的?AsyncLoadingCache?在內部處理了?Future?完成后的這個重入邏輯。

序列化處理:writeReplace

// ... existing code ...Object writeReplace() {return delegate;}
// ... existing code ...

這是一個 Java 序列化的優化。當?AsyncWeigher?對象被序列化時,writeReplace?方法會被調用。它不序列化?AsyncWeigher?本身,而是返回其內部的?delegate?對象。當反序列化時,會得到?delegate?對象。Caffeine 在構建緩存時,如果發現是異步緩存,會再次用?AsyncWeigher?包裝這個?delegate。這樣做可以避免序列化不必要的包裝類,保持序列化數據的簡潔。

總結

AsyncWeigher?是一個巧妙的裝飾器,它解決了在異步緩存中如何計算條目權重這一核心問題。它的策略是:

  • 加載中,權重為0:保護正在進行的計算不被驅逐。
  • 加載完成,計算真實權重:當?Future?完成后,通過委托給用戶提供的?Weigher?來計算真實權重。
  • 依賴重入更新:這個機制依賴于?Future?完成后對緩存條目的更新操作來刷新權重。

通過這種方式,AsyncWeigher?完美地將同步的?Weigher?模型適配到了異步?AsyncCache?的場景中。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/96004.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/96004.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/96004.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C/C++入門之搭建開發環境(VScode篇)

本文主要記錄 Visual Studio Code 中配置 C/C 的開發環境&#xff0c;包括項目設置、編譯選項和調試配置。VScode是編輯器&#xff0c;我們還需要安裝編譯器&#xff0c;才能實現編寫程序到生成可執行文件這一流程。關于編輯器&#xff0c;編譯器和IDE如果有些分不清&#xff0…

【營銷策略算法】關聯規則學習-購物籃分析

Apriori算法是關聯規則學習領域中最經典、最著名的算法之一&#xff0c;用于從大規模數據集中發現有價值的關聯規則。最典型的例子就是購物籃分析&#xff0c;通過分析顧客的購物籃&#xff0c;發現商品之間的關聯關系&#xff0c;從而制定營銷策略&#xff08;如“買尿布的顧客…

行為式驗證碼技術解析:滑塊拼圖、語序選詞與智能無感知

隨著傳統字符驗證碼逐漸被 OCR 與自動化腳本攻破&#xff0c;越來越多業務開始采用 行為式驗證碼 來區分真人與機器。這類驗證碼不僅依賴用戶的操作行為&#xff0c;還結合圖形干擾、環境信息和風控模型&#xff0c;既提升了安全性&#xff0c;也改善了用戶體驗。 常見的實現方…

基于多項式同態加密和秘密共享的JPEG可逆信息隱藏

學習題為《Reversible steganography in cipher domain for JPEG images using polynomial homomorphism》的論文隨著物聯網&#xff08;IoT&#xff09;設備的普及&#xff0c;大量敏感數據&#xff08;如指紋、身份信息&#xff09;需要在云端傳輸和存儲。傳統隱寫技術雖然能…

從 0 到 1 攻克訂單表分表分庫:億級流量下的數據庫架構實戰指南

引言&#xff1a; 本文總字數&#xff1a;約 8500 字建議閱讀時間&#xff1a;35 分鐘 當訂單表撐爆數據庫&#xff0c;我們該怎么辦&#xff1f; 想象一下&#xff0c;你負責的電商平臺在經歷了幾個雙十一后&#xff0c;訂單系統開始頻繁出現問題&#xff1a;數據庫查詢越來…

網絡編程(5)Modbus

【1】Modbus 1. 起源Modbus由Modicon公司于1979年開發&#xff0c;是全球第一個真正用于工業現場的總線協議在中國&#xff0c;Modbus 已經成為國家標準&#xff0c;并有專業的規范文檔&#xff0c;感興趣的可以去查閱相關的文件&#xff0c;詳情如下&#xff1a;標準編號為:GB…

WordPress性能優化全攻略:從插件實戰到系統級優化

一、性能診斷&#xff1a;定位瓶頸是優化第一步 在對 WordPress 進行性能優化前&#xff0c;精準定位性能瓶頸至關重要。這就好比醫生看病&#xff0c;只有先準確診斷&#xff0c;才能對癥下藥。下面將從核心性能指標檢測工具和服務器基礎性能排查兩個方面展開。 1.1 核心性能…

十、網絡與信息安全基礎知識

1 網絡概述 1.1 計算機網絡的概念 1.1.1 計算機網絡的發展 計算機網絡的發展經歷了四個主要階段&#xff1a; 具有通信功能的單機系統&#xff1a; 早期形式&#xff1a;一臺計算機連接多個終端。例子&#xff1a;20 世紀 50 年代的 SAGE 系統。 具有通信功能的多機系統&#x…

校園管理系統|基于SpringBoot和Vue的校園管理系統(源碼+數據庫+文檔)

項目介紹 : SpringbootMavenMybatis PlusVue Element UIMysql 開發的前后端分離的校園管理系統&#xff0c;項目分為管理端和用戶端和院校管理員端 項目演示: 基于SpringBoot和Vue的校園管理系統 運行環境: 最好是java jdk 1.8&#xff0c;我們在這個平臺上運行的。其他版本理…

新后端漏洞(上)- Weblogic SSRF漏洞

漏洞介紹&#xff1a;Weblogic中存在一個SSRF漏洞&#xff0c;利用該漏洞可以發送任意HTTP請求&#xff0c;進而攻擊內網中redis、fastcgi等脆弱組件。編譯及啟動測試環境docker-compose up -d訪問http://127.0.0.1:7001/uddiexplorer/&#xff0c;無需登錄即可查看uddiexplore…

Fiddler 實戰案例解析,開發者如何用抓包工具快速解決問題

在現代軟件開發中&#xff0c;網絡通信問題幾乎是最常見的 Bug 來源。無論是前端調用后端 API、移動端與服務端交互&#xff0c;還是第三方 SDK 請求&#xff0c;都會因為參數錯誤、環境差異、網絡條件不穩定而出現各種難以復現的問題。 在這些場景下&#xff0c;日志往往并不…

【佳易王藥品進銷存軟件實測】:操作簡單 + 全流程管理,醫藥臺賬管理好幫手#軟件教程全解析

前言&#xff1a; &#xff08;一&#xff09;試用版獲取方式 資源下載路徑&#xff1a;進入博主頭像主頁第一篇文章末尾&#xff0c;點擊卡片按鈕&#xff1b;或訪問左上角博客主頁&#xff0c;通過右側按鈕獲取詳細資料。 說明&#xff1a;下載文件為壓縮包&#xff0c;使用…

【設計模式】UML 基礎教程總結(軟件設計師考試重點)

【設計模式】UML 基礎教程總結(軟件設計師考試重點) 統一建模語言(Unified Modeling Language,UML),是一種標準化的面向對象建模語言,用于可視化、規范化和文檔化軟件系統設計。 參考資料:UML基礎教程資料(可用于軟件設計師考試)! (關注不迷路哈!!!) 文章目錄 【…

vite_react 插件 find_code 最終版本

vite_react 插件 find_code 最終版本當初在開發一個大型項目的時候&#xff0c;第一次接觸 vite 構建&#xff0c;由于系統功能很龐大&#xff0c;在問題排查上和模塊開發上比較耗時&#xff0c;然后就開始找解決方案&#xff0c;find-code 插件方案就這樣實現出來了&#xff0…

Python+DRVT 從外部調用 Revit:批量創建梁(2)

接著昨天的示例&#xff0c;繼續創建梁&#xff0c;這次展示以橢圓弧、Nurbs為軸線。 創建以橢圓弧為軸線的梁 橢圓弧曲線的創建&#xff1a; # 創建橢圓弧 def CreateEllipse(ctx : MyContext, z: float) -> DB.Curve:"""create a horizontal partial el…

Flutter × 鴻蒙系統:一文搞懂如何將你的 App 移植到 HarmonyOS!

摘要 Flutter 是一個高效的跨平臺框架&#xff0c;開發者可以使用同一套代碼快速部署到 Android、iOS 等主流平臺。隨著華為鴻蒙系統&#xff08;HarmonyOS&#xff09;的崛起&#xff0c;越來越多開發者希望能將已有的 Flutter 應用遷移到鴻蒙生態中運行。目前&#xff0c;通過…

QML Charts組件之主題與動畫

目錄前言相關系列ChartView 概述&#xff1a;主題與動畫示例一&#xff1a;主題設置&#xff08;ChartTheme.qml&#xff09;圖表與主題設置主題切換部分示例二&#xff1a;動畫設置&#xff08;ChartAnimation.qml&#xff09;圖表與動畫屬性部分分類軸與柱狀圖數據部分交互與…

【論文閱讀】Security of Language Models for Code: A Systematic Literature Review

Security of Language Models for Code: A Systematic Literature Review 該論文于2025年被CCF A類期刊TOSEM收錄&#xff0c;作者來自南京大學和南洋理工大學。 概述 代碼語言模型&#xff08;CodeLMs&#xff09;已成為代碼相關任務的強大工具&#xff0c;其性能優于傳統方法…

[光學原理與應用-422]:非線性光學 - 計算機中的線性與非線性運算

在計算機科學中&#xff0c;線性運算和非線性運算是兩類核心的數學操作&#xff0c;它們在算法設計、數據處理、機器學習等領域有廣泛應用。兩者的核心區別在于是否滿足疊加原理&#xff08;即輸入信號的線性組合的輸出是否等于輸出信號的線性組合&#xff09;。以下是詳細解釋…

Day21_【機器學習—決策樹(3)—剪枝】

決策樹剪枝是一種防止決策樹過擬合的一種正則化方法&#xff1b;提高其泛化能力。決策樹在訓練過程中如果生長過深、過于復雜&#xff0c;會過度擬合訓練數據中的噪聲和異常值&#xff0c;導致在新數據上表現不佳。剪枝通過簡化樹結構&#xff0c;去除不必要的分支&#xff0c;…