Guava提供了Cache接口和相关的类来支持缓存功能,它提供了高性能、线程安全的内存缓存,可以用于优化应用程序的性能。
特点:
相关接口类:
接口/类 | 描述 |
---|---|
Cache |
接口表示一种能够存储键值对的缓存结构 |
LoadingCache |
是 Cache 接口的子接口,用于在缓存中自动加载缓存项 |
CacheLoader |
在使用 LoadingCache 时提供加载缓存项的逻辑 |
CacheBuilder | 用于创建 Cache 和 LoadingCache 实例的构建器类 |
CacheStats | 用于表示缓存的统计信息,如命中次数、命中率、加载次数、存储次数等 |
RemovalListener |
用于监听缓存条目被移除的事件,并在条目被移除时执行相应的操作 |
使用示例:
public static void main(String[] args) throws Exception {
// 创建Cache实例
LoadingCache<String, String> cache = CacheBuilder.newBuilder()
.initialCapacity(2) // 设置初始容量
.concurrencyLevel(4) // 设置并发级别
.maximumSize(5) // 设置最大容量
// .maximumWeight(1000) // 设置最大权重
// .weigher((Weigher) (k, v) -> v.length()) // 设置权重计算器
.expireAfterWrite(Duration.ofSeconds(3)) // 写入后3秒过期
.expireAfterAccess(Duration.ofSeconds(20)) // 访问后20秒过期
.refreshAfterWrite(Duration.ofSeconds(10)) // 写入后自动刷新,3秒刷新一次
.recordStats() // 开启统计信息记录
.removalListener(notification -> { // 设置移除监听
// 缓存Key被移除时触发
String cause = "";
if (RemovalCause.EXPLICIT.equals(notification.getCause())) {
cause = "被显式移除";
} else if (RemovalCause.REPLACED.equals(notification.getCause())) {
cause = "被替换";
} else if (RemovalCause.EXPIRED.equals(notification.getCause())) {
cause = "被过期移除";
} else if (RemovalCause.SIZE.equals(notification.getCause())) {
cause = "被缓存条数超上限移除";
} else if (RemovalCause.COLLECTED.equals(notification.getCause())) {
cause = "被垃圾回收移除";
}
System.out.println(DateUtil.formatDateTime(new Date()) + " Key: " + notification.getKey() + " 移除了, 移除原因: " + cause);
})
.build(new CacheLoader<String, String>() { // 设置缓存重新加载逻辑
@Override
public String load(String key) {
// 重新加载指定Key的值
String newValue = "value" + (int)Math.random()*100;
System.out.println(DateUtil.formatDateTime(new Date()) + " Key: " + key + " 重新加载,新value:" + newValue);
return newValue;
}
});
// 将数据放入缓存
cache.put("key0", "value0");
cache.invalidate("key0");
cache.put("key1", "value1");
cache.put("key1", "value11");
cache.put("key2", "value22");
cache.put("key3", "value3");
cache.put("key4", "value4");
cache.put("key5", "value5");
cache.put("key6", "value6");
cache.put("key7", "value7");
cache.put("key8", "value8");
while (true) {
// 获取数据
System.out.println(DateUtil.formatDateTime(new Date()) + " get key1 value: " + cache.get("key1"));
// 统计信息
System.out.println(DateUtil.formatDateTime(new Date()) + " get stats: " + cache.stats());
Thread.sleep(1000);
}
}
打印日志:
2023-11-24 15:48:17 Key: key0 移除了, 移除原因: 被显式移除
2023-11-24 15:48:17 Key: key1 移除了, 移除原因: 被替换
2023-11-24 15:48:17 Key: key1 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 Key: key2 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 Key: key3 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 Key: key1 重新加载,新value:value0
2023-11-24 15:48:17 Key: key4 移除了, 移除原因: 被缓存条数超上限移除
2023-11-24 15:48:17 get key1 value: value0
2023-11-24 15:48:17 get stats: CacheStats{hitCount=0, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:18 get key1 value: value0
2023-11-24 15:48:18 get stats: CacheStats{hitCount=1, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:19 get key1 value: value0
2023-11-24 15:48:19 get stats: CacheStats{hitCount=2, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3083100, evictionCount=4}
2023-11-24 15:48:20 Key: key5 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key6 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key7 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key8 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key1 移除了, 移除原因: 被过期移除
2023-11-24 15:48:20 Key: key1 重新加载,新value:value0
2023-11-24 15:48:20 get key1 value: value0
2023-11-24 15:48:20 get stats: CacheStats{hitCount=2, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:21 get key1 value: value0
2023-11-24 15:48:21 get stats: CacheStats{hitCount=3, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:22 get key1 value: value0
2023-11-24 15:48:22 get stats: CacheStats{hitCount=4, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=3154100, evictionCount=9}
2023-11-24 15:48:23 Key: key1 移除了, 移除原因: 被过期移除
2023-11-24 15:48:23 Key: key1 重新加载,新value:value0
2023-11-24 15:48:23 get key1 value: value0
2023-11-24 15:48:23 get stats: CacheStats{hitCount=4, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
2023-11-24 15:48:24 get key1 value: value0
2023-11-24 15:48:24 get stats: CacheStats{hitCount=5, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
2023-11-24 15:48:25 get key1 value: value0
2023-11-24 15:48:25 get stats: CacheStats{hitCount=6, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=3208400, evictionCount=10}
......
Cache接口是Guava缓存的核心接口,定义了缓存的基本操作。
它是使用 CacheBuilder 创建的。它提供了基本的缓存操作,如 put、get、delete等方法。同时,Cache 还提供了诸如统计信息、缓存项的值获取方式、缓存项的失效、缓存项的回收等方法,可以满足大多数应用的需求。
主要方法:
方法 | 描述 |
---|---|
V get(K key, Callable extends V> valueLoader) | 根据键获取对应的缓存值,如果缓存中不存在该键,会使用 valueLoader 加载并存储该值。 |
V getIfPresent(K key) | 根据键获取对应的缓存值,如果不存在则返回 null。 |
Map |
获取多个键对应的缓存值的映射,如果缓存中不存在某个键,则该键不会出现在返回的映射中。 |
void put(K key, V value) | 将键值对放入缓存中。如果键已经存在,则会替换对应的值。 |
void putAll(Map extends K, ? extends V> map) | 将多个键值对添加到缓存中。 |
void invalidate(Object key) | 根据键从缓存中移除条目。 |
void invalidateAll(Iterable> keys) | 根据键集合移除多个条目。 |
void invalidateAll() | 移除缓存中的所有条目。 |
long size() | 返回缓存中的条目数。 |
CacheStats stats() | 返回缓存的统计信息。 |
ConcurrentMap |
返回缓存的并发映射视图。 |
void cleanUp() | 执行缓存的清理操作。 |
LoadingCache 继承自 Cache 接口,它是一个带有自动加载功能的缓存接口。
在使用 LoadingCache 时,如果缓存中不存在所需的键值对,则会自动调用CacheLoader的加载方法进行加载,并将加载的结果存入缓存中。
主要方法:
方法 | 说明 |
---|---|
get(K key) | 根据指定的键检索值,如果键不存在,将调用 CacheLoader 进行加载并返回对应的值 |
getAll(Iterable extends K> keys) | 根据给定的键集合批量检索值,并返回一个 Map 对象,对于已缓存的键将直接返回对应的值,对于未缓存的键将通过 CacheLoader 进行加载。 |
getUnchecked(K key) | 获取指定键对应的值,如果缓存中不存在该键,则返回 null,不会触发CacheLoader 加载。 |
refresh(K key) | 刷新指定键对应的值,即使用 CacheLoader 重新加载该键对应的值,并更新缓存。 |
CacheBuilder类是用于创建Guava缓存的构建器。可以使用该类的newBuilder()方法创建一个构建器实例,并通过一系列方法设置缓存的属性,例如最大容量、过期时间等。最后可以通过build()方法构建一个Cache实例。
主要方法:
方法 | 说明 |
---|---|
newBuilder() | 创建一个新的 CacheBuilder 实例 |
from(CacheBuilderSpec spec) | 根据给定的规范字符串创建一个 CacheBuilder 实例 |
from(String spec) | 根据给定的规范字符串创建一个 CacheBuilder 实例 |
initialCapacity(int initialCapacity) | 设置缓存的初始容量 |
concurrencyLevel(int concurrencyLevel) | 设置并发级别,用于估计同时写入的线程数 |
maximumSize(long maximumSize) | 设置缓存的最大容量 |
maximumWeight(long maximumWeight) | 设置缓存的最大权重 |
weigher(Weigher super K1, ? super V1> weigher) | 设置缓存的权重计算器 |
weakKeys() | 使用弱引用存储缓存键(例如,键的引用没有被其他对象引用时,可以被垃圾回收) |
weakValues() | 使用弱引用存储缓存值(例如,值的引用没有被其他对象引用时,可以被垃圾回收) |
softValues() | 使用软引用存储缓存值(例如,当内存不足时,可以被垃圾回收) |
expireAfterWrite(java.time.Duration duration) | 设置写入后过期时间 |
expireAfterWrite(long duration, TimeUnit unit) | 设置写入后过期时间 |
expireAfterAccess(java.time.Duration duration) | 设置访问后过期时间 |
expireAfterAccess(long duration, TimeUnit unit) | 设置访问后过期时间 |
refreshAfterWrite(java.time.Duration duration) | 设置写入后自动刷新时间 |
refreshAfterWrite(long duration, TimeUnit unit) | 设置写入后自动刷新时间 |
ticker(Ticker ticker) | 设置用于衡量缓存时间的时钟源 |
removalListener(RemovalListener super K1, ? super V1> listener) | 设置缓存条目移除监听器 |
recordStats() | 开启缓存统计信息记录 |
build(CacheLoader super K1, V1> loader) | 使用指定的 CacheLoader 构建缓存 |
build() | 构建缓存,如果没有指定 CacheLoader,则需要使用 get 方法手动加载缓存项 |
部分方法详解:
initialCapacity:设置缓存的初始容量
这个方法将通过一个整数值设置缓存的初始大小。它是一个可选的方法,如果没有指定,缓存将采用默认的初始容量。
concurrencyLevel:设置并发级别
用于估计同时写入的线程数。这个方法将通过一个整数值设置并发级别,用于内部数据结构的调整,以提高并发写入的性能。它是一个可选的方法,缺省值为 4。
maximumSize:设置缓存的最大容量
这个方法将通过一个 long 类型的值设置缓存的最大容量。当缓存的条目数达到这个容量时,会触发缓存清除策略来移除一些条目以腾出空间。它是一个可选的方法,如果没有指定最大容量,缓存将不会有大小限制。
maximumWeight:设置缓存的最大权重
这个方法将通过一个 long 类型的值设置缓存的最大权重。权重可以根据缓存条目的大小计算,通常用于缓存对象大小不同的场景。当缓存的总权重达到这个值时,会触发缓存清除策略来移除一些条目以腾出空间。它是一个可选的方法,如果没有指定最大权重,缓存将不会有权重限制。
weigher:设置缓存的权重计算器
这个方法将通过一个实现了 Weigher 接口的对象设置缓存的权重计算器。通过权重计算器,可以根据缓存条目的键和值来计算它们的权重,以便在达到最大权重时触发缓存清除策略。它是一个可选的方法,如果没有设置权重计算器,缓存将不会有权重限制。
expireAfterWrite:设置写入后过期时间
这个方法通过一个 java.time.Duration 对象设置缓存条目的写入后过期时间。过期时间从最后一次写入条目开始计算。一旦超过指定的时间,条目将被认为是过期的并被清除。这是一个可选的方法,如果没有指定过期时间,条目将不会主动过期。
expireAfterAccess:设置访问后过期时间
这个方法通过一个 java.time.Duration 对象设置缓存条目的访问后过期时间。过期时间从最后一次访问条目开始计算。一旦超过指定的时间,条目将被认为是过期的并被清除。这是一个可选的方法,如果没有指定过期时间,条目将不会主动过期。
refreshAfterWrite:设置写入后自动刷新时间
这个方法通过一个 java.time.Duration 对象设置缓存条目的自动刷新时间。自动刷新时间从最后一次写入条目开始计算。一旦超过指定的时间,当条目被访问时,缓存将自动刷新该条目,即会调用 CacheLoader 的 load 方法重新加载该条目。这是一个可选的方法,如果没有设置自动刷新时间,条目将不会自动刷新。
recordStats():开启缓存统计信息记录
这个方法用于开启缓存的统计信息记录功能。一旦开启,可以通过 Cache.stats() 方法获取缓存的统计信息,如命中率、加载次数、平均加载时间等。这是一个可选的方法,如果不开启统计信息记录,将无法获取缓存的统计信息。
注意事项:
maximumSize与maximumWeight不能同时设置
设置maximumWeight时必须设置weigher
当缓存失效后,refreshAfterWrite设置的写入后自动刷新时间不会再有用
注意:expireAfterWrite、expireAfterAccess、refreshAfterWrite三个值的使用
开启recordStats后,才进行统计
CacheLoader 可以被视为一种从存储系统(如磁盘、数据库或远程节点)中加载数据的方法。
CacheLoader 通常搭配refreshAfterWrite使用,在写入指定的时间周期后会调用CacheLoader 的load方法来获取并刷新为新值。
load 方法在以下情况下会被触发调用:
当设置了refreshAfterWrite(写入后自动刷新时间),达到自动刷新时间时,会调用 load 方法来重新加载该键的值。
调用 Cache.get(key) 方法获取缓存中指定键的值时,如果该键的值不存在,则会调用 load 方法来加载该键的值。
调用 Cache.get(key, callable) 方法获取缓存中指定键的值时,如果该键的值存在,则直接返回;如果该键的值不存在,则会调用 callable 参数指定的回调函数来计算并加载该键的值。
调用 Cache.getUnchecked(key) 方法获取缓存中指定键的值时,无论该键的值存在与否,都会调用 load 方法来加载该键的值。
需要注意的是,当调用 load 方法加载缓存值时,可能会发生 IO 操作或其他耗时操作,因此建议在加载操作中使用异步方式来避免阻塞主线程。另外,加载操作的实现要考虑缓存的一致性和并发性,避免多个线程同时加载同一个键的值。
CacheStats 对象提供了诸如缓存命中率、加载缓存项数、缓存项回收数等统计信息的访问。
它可以通过 Cache.stats() 方法来获取,从而方便开发者监控缓存状态。
主要属性:
属性 | 描述 |
---|---|
hitCount | 缓存命中次数。表示从缓存中成功获取数据的次数 |
missCount | 缓存未命中次数。表示从缓存中未能获取到数据的次数 |
loadSuccessCount | 加载数据成功次数。表示通过 CacheLoader 成功加载数据的次数 |
loadExceptionCount | 加载数据异常次数。表示通过 CacheLoader 加载数据时发生异常的次数 |
totalLoadTime | 加载数据总耗时。表示通过 CacheLoader 加载数据的总时间 |
evictionCount | 缓存项被移除的次数,只记录因空超过设置的最大容量而进行缓存项移除的次数 |
RemovalListener 用于在缓存中某个值被移除时执行相应的回调操作。
可以使用 CacheBuilder.removalListener() 方法为缓存设置 RemovalListener。
RemovalListener 的使用:
创建一个实现 RemovalListener 接口的类,实现 onRemoval 方法。这个方法会在缓存项被移除时被调用,接受两个参数: key 和 value。key 是被移除的缓存项的键,value 是被移除的缓存项的值。你可以根据需要在 onRemoval 方法中实现自定义的逻辑。
使用 CacheBuilder 的 removalListener 方法,将创建的 RemovalListener 对象传递给它。
缓存项被移除时,onRemoval 方法会自动被调用,方法会传入一个RemovalNotification 类型的参数,里面包含相应的 key 和 value等信息。你可以在这个方法中执行自定义的业务逻辑,例如日志记录、资源清理等操作。
RemovalNotification:
RemovalNotification 是 Guava 中用于表示缓存项被移除的通知的类。当在 Guava Cache 中注册了 RemovalListener 后,RemovalNotification 对象会在缓存项被移除时传递给 RemovalListener 的 onRemoval 方法。
RemovalNotification 包含了有关被移除缓存项的一些重要信息,例如键、值以及移除原因。下面是 RemovalNotification 类中常用的属性和方法:
getKey():获取被移除的缓存项的键。
getValue():获取被移除的缓存项的值。
getCause():获取移除原因,它是一个枚举类型RemovalCause,表示缓存项被移除的原因。
RemovalCause的可选值:
使用 RemovalNotification 可以让你在缓存项被移除时获取相关信息,并根据移除原因采取适当的处理措施。例如,你可以根据移除原因记录日志、执行清理操作、发送通知等。这样能够增强缓存的功能和可观察性。
注意事项:
使用 RemovalListener 可以方便地在缓存项被移除时执行一些自定义的操作,例如清理相关资源、更新其他缓存或发送通知等。根据实际需要,合理利用 RemovalListener 可以增强缓存的功能和灵活性。