Google 出的 Guava 是个什么鬼?

本文深入探讨Google Guava库中的Cache组件,分析其在Java开发中的应用场景及实现原理,特别是自动清除过期数据的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

640?wx_fmt=jpeg

前言

Google 出的 Guava 是 Java 核心增强的库,应用非常广泛。

我平时用的也挺频繁,这次就借助日常使用的 Cache 组件来看看 Google 大牛们是如何设计的。

缓存

本次主要讨论缓存。

缓存在日常开发中举足轻重,如果你的应用对某类数据有着较高的读取频次,并且改动较小时那就非常适合利用缓存来提高性能。

缓存之所以可以提高性能是因为它的读取效率很高,就像是 CPU 的 L1L2L3 缓存一样,级别越高相应的读取速度也会越快。

但也不是什么好处都占,读取速度快了但是它的内存更小资源更宝贵,所以我们应当缓存真正需要的数据。

其实也就是典型的空间换时间。

下面谈谈 Java 中所用到的缓存。

JVM 缓存

首先是 JVM 缓存,也可以认为是堆缓存。

其实就是创建一些全局变量,如 MapList 之类的容器用于存放数据。

这样的优势是使用简单但是也有以下问题:

  • 只能显式的写入,清除数据。

  • 不能按照一定的规则淘汰数据,如 LRULFUFIFO 等。

  • 清除数据时的回调通知。

  • 其他一些定制功能等。

Ehcache、Guava Cache

所以出现了一些专门用作 JVM 缓存的开源工具出现了,如本文提到的 Guava Cache。

它具有上文 JVM 缓存不具有的功能,如自动清除数据、多种清除算法、清除回调等。

但也正因为有了这些功能,这样的缓存必然会多出许多东西需要额外维护,自然也就增加了系统的消耗。

分布式缓存

刚才提到的两种缓存其实都是堆内缓存,只能在单个节点中使用,这样在分布式场景下就招架不住了。

于是也有了一些缓存中间件,如 Redis、Memcached,在分布式环境下可以共享内存。

具体不在本次的讨论范围。

Guava Cache 示例

之所以想到 Guava 的 Cache,也是最近在做一个需求,大体如下:

从 Kafka 实时读取出应用系统的日志信息,该日志信息包含了应用的健康状况。 如果在时间窗口 N 内发生了 X 次异常信息,相应的我就需要作出反馈(报警、记录日志等)。

对此 Guava 的 Cache 就非常适合,我利用了它的 N 个时间内不写入数据时缓存就清空的特点,在每次读取数据时判断异常信息是否大于 X 即可。

伪代码如下:

640?wx_fmt=png

首先是构建了 LoadingCache 对象,在 N 分钟内不写入数据时就回收缓存(当通过 Key 获取不到缓存时,默认返回 0)。

然后在每次消费时候调用 checkAlert() 方法进行校验,这样就可以达到上文的需求。

我们来设想下 Guava 它是如何实现过期自动清除数据,并且是可以按照 LRU 这样的方式清除的。

大胆假设下:

内部通过一个队列来维护缓存的顺序,每次访问过的数据移动到队列头部,并且额外开启一个线程来判断数据是否过期,过期就删掉。有点类似于我之前写过的 动手实现一个 LRU cache

胡适说过:大胆假设小心论证

下面来看看 Guava 到底是怎么实现。

原理分析

看原理最好不过是跟代码一步步走了:

示例代码在这里:

https://github.com/crossoverJie/Java-Interview/blob/master/src/main/java/com/crossoverjie/guava/CacheLoaderTest.java

640?wx_fmt=jpeg

为了能看出 Guava 是怎么删除过期数据的在获取缓存之前休眠了 5 秒钟,达到了超时条件。

640?wx_fmt=jpeg

最终会发现在 com.google.common.cache.LocalCache 类的 2187 行比较关键。

再跟进去之前第 2182 行会发现先要判断 count 是否大于 0,这个 count 保存的是当前缓存的数量,并用 volatile 修饰保证了可见性。

更多关于 volatile 的相关信息可以查看 你应该知道的 volatile 关键字

接着往下跟到:

640?wx_fmt=jpeg

2761 行,根据方法名称可以看出是判断当前的 Entry 是否过期,该 entry 就是通过 key 查询到的。

640?wx_fmt=jpeg

这里就很明显的看出是根据根据构建时指定的过期方式来判断当前 key 是否过期了。

640?wx_fmt=jpeg

如果过期就往下走,尝试进行过期删除(需要加锁,后面会具体讨论)。

640?wx_fmt=jpeg

到了这里也很清晰了:

  • 获取当前缓存的总数量

  • 自减一(前面获取了锁,所以线程安全)

  • 删除并将更新的总数赋值到 count。

其实大体上就是这个流程,Guava 并没有按照之前猜想的另起一个线程来维护过期数据。

应该是以下原因:

  • 新起线程需要资源消耗。

  • 维护过期数据还要获取额外的锁,增加了消耗。

而在查询时候顺带做了这些事情,但是如果该缓存迟迟没有访问也会存在数据不能被回收的情况,不过这对于一个高吞吐的应用来说也不是问题。

总结

最后再来总结下 Guava 的 Cache。

其实在上文跟代码时会发现通过一个 key 定位数据时有以下代码:

640?wx_fmt=jpeg

如果有看过 ConcurrentHashMap 的原理 应该会想到这其实非常类似。

其实 Guava Cache 为了满足并发场景的使用,核心的数据结构就是按照 ConcurrentHashMap 来的,这里也是一个 key 定位到一个具体位置的过程。

先找到 Segment,再找具体的位置,等于是做了两次 Hash 定位。

上文有一个假设是对的,它内部会维护两个队列 accessQueue,writeQueue 用于记录缓存顺序,这样才可以按照顺序淘汰数据(类似于利用 LinkedHashMap 来做 LRU 缓存)。

同时从上文的构建方式来看,它也是构建者模式来创建对象的。

因为作为一个给开发者使用的工具,需要有很多的自定义属性,利用构建则模式再合适不过了。

Guava 其实还有很多东西没谈到,比如它利用 GC 来回收内存,移除数据时的回调通知等。之后再接着讨论。

END


640?wx_fmt=png

更多精彩内容,尽在阅读原文

<think>好的,我现在需要回答用户关于Guava和Java区别的问题,还要解释它们是否有关联。首先,我得确认自己对这两个概念的理解是否正确。 用户提到Guava和Java都有“ava”,可能以为它们有联系。我知道Java是一种编程语言,而Guava是一个库,但具体细节可能需要进一步确认。首先,我应该明确两者的定义和用途。 Java是一种广泛使用的高级编程语言,由Sun Microsystems开发,现在属于Oracle。它用于构建各种应用程序,从桌面到Web再到移动应用,特别是Android开发。Java的特点包括面向对象、平台无关性、自动内存管理等。 Guava呢?我记得是Google的一个开源Java库,提供了很多工具类和实用功能,用于简化Java编程中的常见任务,比如集合处理、缓存、函数式编程支持等。比如Guava的集合工具比Java标准库更强大,有不可变集合、Multimap等。 接下来,用户问它们的区别。我需要分点列,可能包括性质、功能、维护者、依赖关系等。比如Java是语言,Guava是基于Java的库;Java提供基础功能,Guava扩展了这些功能。 然后关联性方面,Guava是专门为Java设计的库,所以自然有关联。但“ava”只是巧合,Guava名字可能来源于水果名,或者其他原因,需要查证。 可能需要确认Guava名字的来源,避免错误。查资料发现,Guava原本是Google的核心库,被称为Google Core Libraries for Java,后来开源并改名Guava,名字确实来自热带水果,和Java的命名传统(如Java本身源自咖啡)可能有相似之处,但两者没有直接联系。 此外,需要举例说明Guava提供的功能,比如集合工具、缓存机制,这些在标准Java库中没有或不够完善。比如ImmutableList,或者Joiner类处理字符串拼接。 还要注意用户可能混淆名称,所以明确指名称相似是巧合,但Guava是建立在Java之上的工具库,增强Java的功能。 总结回答结构:先介绍各自是什么,再分点区别,然后说明关联,最后总结名称的巧合和实际关系。确保用中文,行内公式正确,结构清晰。</think>Guava和Java虽然名称中都有"ava"字母组合,但本质上是两个不同的概念,既有明显区别也存在密切关联。以下是分点说明: **一、基础定义区别** - **Java**:由Sun Microsystems(现Oracle)开发的高级编程语言(诞生于1995年),具备`Write Once, Run Anywhere`特性,如: ```java public class HelloWorld { public static void main(String[] args) { System.out.println("Hello Java!"); } } ``` - **Guava**:Google开源的**Java工具库**(发布于2007年),通过`pom.xml`依赖引入: ```xml <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>31.1-jre</version> </dependency> ``` **二、核心差异对比表** | 特性 | Java | Guava | |---------------------|-------------------------------------|------------------------------------| | 性质 | 编程语言 | Java类库 | | 原生支持 | 语法、JVM、基础API | 增强工具(如`ImmutableList.copyOf()`) | | 字符串处理 | `String.split()`基础功能 | `Splitter.on(',').trimResults()` | | 集合扩展 | `java.util.Collections` | `Multimap`, `BiMap`等高级结构 | | 函数式编程 | Lambda表达式(Java 8+) | `Function`/`Predicate`接口早期支持 | **三、关联性说明** 1. **依赖关系**:Guava完全基于Java编写,需在Java环境中运行(例如使用Java 8编译Guava 31.1需添加`-source 8`参数) 2. **功能互补**:Guava填补了Java标准库的空白,例如: - 更安全的空值处理(`Optional<T>`在Java 8前由Guava提供) - 高效缓存实现(`CacheBuilder`对比`WeakHashMap`) 3. **数学计算增强**:Guava提供`DoubleMath.fuzzyEquals()`等实用方法,解决浮点数精度问题 **四、名称渊源** - **Java**:名称源自印尼爪哇岛(盛产咖啡,Logo采用咖啡杯形象) - **Guava**:名称源于热带水果番石榴,延续Google内部项目命名传统(如Bigtable、MapReduce) **五、典型场景对比** - **原生Java实现字符串拼接**: ```java StringBuilder sb = new StringBuilder(); for (String item : list) { if (sb.length() > 0) sb.append(", "); sb.append(item); } ``` - **Guava优化实现**: ```java String joined = Joiner.on(", ").skipNulls().join(list); ``` 总结:Guava是Java生态中的重要工具库,其名称中的"ava"是巧合,实际关系类似于"汽车(Java)"与"高级车载系统(Guava)"——后者通过扩展功能提升前者的使用体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值