🌟 引言

在软件开发过程中,缓存是提升系统性能的常用手段。对于基础场景,直接使用 Java集合框架(如Map/Set/List)即可满足需求。然而,当面对更复杂的缓存场景时:

  • 需要支持多种过期策略(基于时间、访问频率等)
  • 要求自动淘汰机制
  • 需要线程安全等高级特性

自行实现这些功能往往复杂度较高。本文将介绍 Java 生态中成熟的两大主流本地缓存解决方案:Caffeine(新一代缓存之王)和Guava Cache(经典缓存方案)。

📊 核心维度对比

评估维度CaffeineGuava Cache
性能⚡ 读写吞吐量高5-10倍🐢 中等性能
内存效率🧠 更低内存占用(优化数据结构)📦 较高内存消耗
并发能力🚀 无锁算法,百万级QPS🔒 分段锁,十万级QPS
淘汰算法🎯 TinyLFU + LRU 自适应⏳ 标准LRU
监控统计📈 内置详细指标📊 基础统计
JDK兼容性Java 8+Java 6+
社区活跃度🌟 持续更新(2023年仍有新版本)🛑 维护模式(仅修复bug)

🚀 Caffeine

Caffeine 是一个性能ISS(In-Space Sizing)的缓存框架,它使用无锁算法和分段锁机制,以更优的方式优化了缓存淘汰算法。Caffeine 的设计目标为极致性能,并针对一些常见的场景进行了优化。

🌟 特性

  • 无锁算法和分段锁机制,以更优的方式优化了缓存淘汰算法。
  • 高命中率,通过优化淘汰算法,Caffeine 显著提高缓存命中率。
  • 更低内存开销,Caffeine 使用更小的内存结构,从而减少内存消耗。
  • 线程安全,Caffeine 支持并发操作,保证线程安全。

🌟 如何使用

<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>3.2.0</version>
</dependency>
import com.github.benmanes.caffeine.cache.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CompletableFuture;public class CaffeineDemo {public static void main(String[] args) {basicUsageDemo();loadingCacheDemo();asyncLoadingCacheDemo();evictionDemo();statisticsDemo();}/*** 基础缓存操作示例*/public static void basicUsageDemo() {System.out.println("\n=== 1. 基础缓存操作 ===");Cache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS) // 写入5秒后过期.maximumSize(100)                     // 最大100个条目.build();// 手动写入cache.put("key1", "value1");// 获取值(不存在返回null)String value = cache.getIfPresent("key1");System.out.println("获取key1: " + value);  // 输出: value1// 获取或计算(线程安全)String value2 = cache.get("key2", k -> "computed-" + k);System.out.println("获取key2: " + value2); // 输出: computed-key2}/*** 自动加载缓存示例*/public static void loadingCacheDemo() {System.out.println("\n=== 2. 自动加载缓存 ===");LoadingCache<String, String> cache = Caffeine.newBuilder().expireAfterAccess(3, TimeUnit.SECONDS) // 3秒未访问则过期.maximumSize(10).build(key -> {// 模拟从数据库加载System.out.println("正在加载: " + key);return "db-value-" + key;});// 自动触发加载函数System.out.println(cache.get("user1001")); // 输出: db-value-user1001System.out.println(cache.get("user1001")); // 第二次直接从缓存获取}/*** 异步加载缓存示例*/public static void asyncLoadingCacheDemo() {System.out.println("\n=== 3. 异步加载缓存 ===");AsyncLoadingCache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).maximumSize(1000).buildAsync(key -> {// 模拟异步加载return CompletableFuture.supplyAsync(() -> {System.out.println("异步加载: " + key);return "async-value-" + key;});});// 异步获取cache.get("id123").thenAccept(value -> {System.out.println("异步获取结果: " + value); // 输出: async-value-id123});}/*** 淘汰策略示例*/public static void evictionDemo() {System.out.println("\n=== 4. 淘汰策略 ===");Cache<String, String> cache = Caffeine.newBuilder().maximumSize(3) // 测试用的小容量.removalListener((key, value, cause) -> System.out.printf("淘汰事件: key=%s, 原因=%s\n", key, cause)).build();cache.put("k1", "v1");cache.put("k2", "v2");cache.put("k3", "v3");cache.put("k4", "v4"); // 触发淘汰(LRU)System.out.println("当前大小: " + cache.estimatedSize()); // 输出: 3}/*** 统计功能示例*/public static void statisticsDemo() {System.out.println("\n=== 5. 统计功能 ===");Cache<String, String> cache = Caffeine.newBuilder().maximumSize(100).recordStats() // 开启统计.build();cache.put("k1", "v1");cache.getIfPresent("k1");cache.getIfPresent("missingKey");CacheStats stats = cache.stats();System.out.println("命中率: " + stats.hitRate());    // 输出: 0.5System.out.println("命中数: " + stats.hitCount());    // 输出: 1System.out.println("未命中数: " + stats.missCount()); // 输出: 1}
}

🚀 Guava Cache

Guava Cache 是 Google 官方提供的一个缓存框架,它提供了许多高级特性,如自动加载、统计、序列化、并发控制等。与 Caffeine 不同,Guava Cache 的设计目标为简单易用,并支持更多的高级特性。

🌟 特性

  • 自动加载、统计、序列化、并发控制等高级特性。
  • 更高的并发控制,Guava Cache 使用更复杂的并发控制机制,以更优的方式解决并发问题。

🌟 如何使用

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>33.4.8-jre</version>
</dependency>
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.*;
import io.vavr.collection.List;
public class GuavaCacheDemo {public static void main(String[] args) throws ExecutionException {basicUsageDemo();loadingCacheDemo();cacheRemovalListenerDemo();cacheStatisticsDemo();advancedEvictionDemo();}/*** 基础缓存操作示例*/public static void basicUsageDemo() {System.out.println("\n=== 1. 基础缓存操作 ===");Cache<String, String> cache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS) // 写入5秒后过期.maximumSize(100) // 最大100个条目.concurrencyLevel(4) // 并发级别.build();// 手动写入cache.put("key1", "value1");// 获取值(不存在返回null)String value = cache.getIfPresent("key1");System.out.println("获取key1: " + value); // 输出: value1// 尝试获取不存在的keyString value2 = cache.getIfPresent("key2");System.out.println("获取不存在的key2: " + value2); // 输出: null}/*** 自动加载缓存示例*/public static void loadingCacheDemo() throws ExecutionException {System.out.println("\n=== 2. 自动加载缓存 ===");LoadingCache<String, String> cache = CacheBuilder.newBuilder().expireAfterAccess(3, TimeUnit.SECONDS) // 3秒未访问则过期.maximumSize(10).build(new CacheLoader<String, String>() {@Overridepublic String load(String key) {// 模拟从数据库加载System.out.println("正在加载: " + key);return "db-value-" + key;}});// 自动触发加载函数System.out.println(cache.get("user1001")); // 输出: db-value-user1001System.out.println(cache.get("user1001")); // 第二次直接从缓存获取// 批量获取System.out.println(cache.getAll(List.of("user1002", "user1003")));}/*** 缓存淘汰监听器示例*/public static void cacheRemovalListenerDemo() {System.out.println("\n=== 3. 淘汰监听器 ===");RemovalListener<String, String> listener = notification -> {System.out.printf("淘汰事件: key=%s, value=%s, 原因=%s\n", notification.getKey(), notification.getValue(),notification.getCause());};Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(3) // 测试用的小容量.removalListener(listener).build();cache.put("k1", "v1");cache.put("k2", "v2");cache.put("k3", "v3");cache.put("k4", "v4"); // 触发淘汰(LRU)cache.invalidate("k2"); // 手动触发淘汰}/*** 缓存统计示例*/public static void cacheStatisticsDemo() {System.out.println("\n=== 4. 缓存统计 ===");Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(100).recordStats() // 开启统计.build();cache.put("k1", "v1");cache.getIfPresent("k1");cache.getIfPresent("missingKey");CacheStats stats = cache.stats();System.out.println("命中率: " + stats.hitRate()); // 输出: 0.5System.out.println("命中数: " + stats.hitCount()); // 输出: 1System.out.println("未命中数: " + stats.missCount()); // 输出: 1System.out.println("加载成功数: " + stats.loadSuccessCount());}/*** 高级淘汰策略示例*/public static void advancedEvictionDemo() {System.out.println("\n=== 5. 高级淘汰策略 ===");Cache<String, String> cache = CacheBuilder.newBuilder()// 基于权重的淘汰(假设不同value占用不同空间).maximumWeight(1000).weigher((String key, String value) -> value.length())// 弱引用key和value(适合缓存大对象).weakKeys().weakValues()// 定期维护(减少并发开销).concurrencyLevel(8).build();cache.put("long", "这是一个很长的字符串值");cache.put("short", "小");System.out.println("当前大小: " + cache.size());}
}

🎉 结论

对于大多数现代 Java 应用,Caffeine 无疑是更优选择,其卓越的性能表现和更低的内存开销使其成为新项目的首选。而 Guava Cache 则更适合已有 Guava 生态的遗留系统,或者需要特定功能(如 CacheLoader 深度集成)的场景。

终极建议: 新项目直接采用 Caffeine,老项目若无性能瓶颈可继续使用 Guava Cache,在遇到性能问题时再考虑迁移。两者 API 相似,迁移成本较低。

🚀 公众号【会飞的架构师】关注并发送:面试。领取私人技术资料

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/84322.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/84322.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/84322.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDA插件 MIPSROP的安装和使用方法

前言 笔者的IDA版本为9.0&#xff0c;刚开始根据一些博客描述以为将mipsrop.py拷贝到IDA的plugins目录即可&#xff0c;可操作后发现事情好像没这么简单&#xff0c;复制进去后就发现没有博客中所说的 MIPS ROP Finder &#xff0c;笔者在网上搜索了很多博客后在 https://bbs.…

(1)转置后,行列式的值不变 (2)将行列式的任意两行互换位置后,行列式改变符号

以下是对原始内容在不改变内容本身的前提下进行的格式优化&#xff0c;以提升可读性和逻辑清晰度&#xff1a; ✅ 行列式的几何意义 行列式&#xff08;determinant&#xff09;是线性代数中一个非常重要的概念&#xff0c;它的几何含义可以从以下几个方面理解&#xff1a; &a…

最大似然估计(Maximum Likelihood Estimation, MLE)详解

一、定义 最大似然估计 是一种参数估计方法&#xff0c;其核心思想是&#xff1a; 选择能使观测数据出现概率最大的参数值作为估计值。 具体来说&#xff0c;假设数据 D x 1 , x 2 , … , x n D{x_1,x_2,…,x_n} Dx1​,x2​,…,xn​独立且服从某个概率分布 P ( x ∣ θ ) P(…

用go从零构建写一个RPC(3)--异步调用+多路复用实现

在前两个版本中&#xff0c;我们实现了基础的客户端-服务端通信、连接池、序列化等关键模块。为了进一步提升吞吐量和并发性能&#xff0c;本版本新增了 异步发送机制 和 多路复用支持&#xff0c;旨在减少资源消耗、提升连接利用率。 代码地址&#xff1a;https://github.com/…

FFmpeg 安装包全攻略:gpl、lgpl、shared、master 区别详解

这些 FFmpeg 安装包有很多版本和变种&#xff0c;主要区别在于以下几个方面&#xff1a; ✅ 一、从名称中看出的关键参数&#xff1a; 1. 版本号 master&#xff1a;开发版&#xff0c;最新功能&#xff0c;但可能不稳定。n6.1 / n7.1&#xff1a;正式版本&#xff0c;更稳定…

深度学习实战:从图像分类到文本生成的完整案例解析

1 图像分类案例 1.1 CIFAR10数据集介绍 cifar数据是torchvision第三方包提供的数据集 训练集5w 测试集1w y标签 10个类别 10分类问题 一张图形状 (32, 32, 3) import torch import torch.nn as nn from torchvision.datasets import CIFAR10 from torchvision.transforms i…

Android 添加系统服务的完整流程

[应用程序] (应用进程)│↓ 调用简单API [SoundManager] │ ├─ 代理模式门面模式&#xff08;应用进程&#xff09;│ ├─ 缓存数据 ←─ 装饰器模式&#xff08;应用进程&#xff09;│ └─ 转换异常 ←─ 适配器模式&#xff08;应用进程&#xff09;│↓ 通过Bind…

wan2.1代码笔记

GPU内存不够&#xff0c;可以先运行umt5&#xff0c;然后再运行wanpipeline&#xff0c;参考FLUX.1代码笔记&#xff0c;或者使用ComfyUI。 下面使用随机数代替umt5 embedding。 import torch from diffusers.utils import export_to_video from diffusers import Autoencoder…

环境搭建与工具配置

3.1 本地环境搭建 3.1.1 WAMP环境搭建漏洞靶场&#xff08;一、二&#xff09; WAMP&#xff08;Windows Apache MySQL PHP&#xff09;是搭建本地Web漏洞靶场的基础环境。 安装步骤&#xff1a; Apache&#xff1a;下载并安装最新版Apache HTTP Server&#xff0c;配置监…

STM32F446主时钟失效时DAC输出异常现象解析与解决方案

—### 现象概述 在STM32F446微控制器应用中&#xff0c;若主时钟&#xff08;HSE&#xff09;的晶体信号对地短路&#xff0c;但DAC&#xff08;数模转换器&#xff09;仍能输出变化信号&#xff0c;这一现象看似矛盾&#xff0c;实则与系统时钟切换机制密切相关。本文将从硬件…

React 如何封装一个可复用的 Ant Design 组件

文章目录 前言一、为什么需要封装组件&#xff1f;二、 仿antd组件的Button按钮三、封装一个可复用的表格组件 (实战)1. 明确需求2. 设计组件 API3. 实现组件代码4. 使用组件 三、封装组件的最佳实践四、进阶优化 总结 前言 作为一名前端开发工程师&#xff0c;在日常项目中&a…

STC89C52RC/LE52RC

STC89C52RC 芯片手册原理图扩展版原理图 功能示例LED灯LED灯的常亮效果LED灯的闪烁LED灯的跑马灯效果&#xff1a;从左到右&#xff0c;从右到左 数码管静态数码管数码管计数mian.cApp.cApp.hCom.cCom.hDir.cDir.hInt.cInt.hMid.cMid.h 模板mian.cApp.cApp.hCom.cCom.hDir.cDir…

踩坑记录:RecyclerView 局部刷新notifyItemChanged多次调用只触发一次 onBindViewHolder 的原因

1. 问题背景 在做项目的时候&#xff0c;RecyclerView需要使用局部刷新&#xff0c;使用 notifyItemChanged(position, payload) 实现局部刷新&#xff0c;但发现调用多次只执行了一次&#xff0c;第二个刷新不生效。 2. 错误示例&#xff08;只处理 payloads.get(0)&#xff…

OpenLayers 加载鹰眼控件

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图控件是一些用来与地图进行简单交互的工具&#xff0c;地图库预先封装好&#xff0c;可以供开发者直接使用。OpenLayers具有大部分常用的控件&#x…

WPF···

设置启动页 默认最后一个窗口关闭,程序退出,可以设置 修改窗体的icon图标 修改项目exe图标 双击项目名会看到代码 其他 在A窗体点击按钮打开B窗体,在B窗体设置WindowStartupLocation=“CenterOwner” 在A窗体的代码设置 B.Owner = this; B.Show(); B窗体生成在A窗体中间…

github公开项目爬取

import requestsdef search_github_repositories(keyword, tokenNone, languageNone, max_results1000):"""通过 GitHub API 搜索仓库&#xff0c;支持分页获取所有结果&#xff08;最多 1000 条&#xff09;:param keyword: 搜索关键词:param token: GitHub To…

防震基座在半导体晶圆制造设备抛光机详细应用案例-江苏泊苏系统集成有限公司

在半导体制造领域&#xff0c;晶圆抛光作为关键工序&#xff0c;对设备稳定性要求近乎苛刻。哪怕极其细微的振动&#xff0c;都可能对晶圆表面质量产生严重影响&#xff0c;进而左右芯片制造的成败。以下为您呈现一个防震基座在半导体晶圆制造设备抛光机上的经典应用案例。 企…

S32K开发环境搭建详细教程(一、S32K IDE安装注册)

一、S32K IDE安装注册 1、进入恩智浦官网https://www.nxp.com.cn/&#xff08;需要在官网注册一个账号&#xff09; 2、直接搜索 “Standard Software”&#xff0c;找到S32K3 Standard Software&#xff0c;点击进入 3、下载 (1)Automotive SW - S32K3 - S32 Design Studio…

Spring Cloud Gateway 微服务网关实战指南

上篇文章简单介绍了SpringCloud系列OpenFeign的基本用法以及Demo搭建&#xff08;Spring Cloud实战&#xff1a;OpenFeign远程调用与服务治理-CSDN博客&#xff09;&#xff0c;今天继续讲解下SpringCloud Gateway实战指南&#xff01;在分享之前继续回顾下本次SpringCloud的专…

MSP430G2553 USCI模块串口通信

1.前言 最近需要利用msp430连接蓝牙模块传递数据&#xff0c;于是死磕了一段时间串口&#xff0c;在这里记录一下 2.msp430串口模块 msp430的串口模块可以有USCI模块提供 在异步模式中&#xff0c; USCI_Ax 模块通过两个外部引脚&#xff0c; UCAxRXD 和 UCAxTXD&#xff0…