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;…