本文主要是介绍Caffeine - Home,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Caffeine - Home
- 1. 缓存条目自动加载
- 2. 缓存条目异步加载
- 3. 根据访问频率和新近度的剔除策略
- 4. 基于最后访问时间的剔除策略
- 5. 条目删除(移除)通知
- 6. 条目写入传播到外部资源
- 7. 缓存累积访问量统计
Caffeine是基于Java 8的高性能缓存库,可提供接近最佳的命中率。
缓存与ConcurrentMap类似,但又不尽相同。其中最根本的区别是ConcurrentMap会持有所有添加到其中的条目,直到将其明确删除为止。而缓存通常可以通过配置实现条目的自动剔除,以限制其内存占用量。但在某些情况下,即使没有条目剔除功能,得益于缓存自动加载功能的实现,LoadCache或AsyncLoadingCache也非常有用。
Caffeine提供灵活的构建器来创建具有以下特征组合的缓存:
- 缓存条目的自动加载,支持异步加载;
- 超过缓存最大容量时,依据访问频率和新近度执行基于容量的剔除策略
- 通过计算最后访问或写入时点以来的时长执行基于时间的失效策略
- 条目会在下一个请求到来时进行异步刷新
- 通过弱引用自动包装keys
- 通过弱引用和软引用自动包装values
- 条目剔除(或移除)通知
- 条目写入传播到外部资源
- 缓存累积访问量统计
为了提高集成度,扩展模块中提供了JSR-107 JCache和Guava适配器。JSR-107提供了基于Java 6的标准API,以牺牲功能和性能为代价,最大限度的减少了供应商特定的代码。Guava是Caffeine的前身,Guava适配器提供了一种简单的迁移策略。
欢迎称为贡献者。请阅读设计文档,开发安装指南和路线图。
1. 缓存条目自动加载
Caffeine提供了CacheLoader
接口,用于定义缓存条目的自动加载逻辑。你可以实现CacheLoader
接口,并在构建Caffeine缓存时使用.build(CacheLoader)
方法将其传递给Caffeine。这样,当缓存中不存在某个键的条目时,Caffeine会自动调用CacheLoader
的load
方法来加载该条目。
以下是一个示例代码,演示如何使用Caffeine实现缓存条目的自动加载:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;public class AutoLoadingCacheExample {public static void main(String[] args) {// 创建自动加载缓存Cache<String, String> cache = Caffeine.newBuilder().maximumSize(100).expireAfterWrite(10, TimeUnit.MINUTES).build(key -> fetchDataFromDatabase(key));// 从缓存中获取数据String value1 = cache.get("key1");String value2 = cache.get("key2");System.out.println("Value 1: " + value1);System.out.println("Value 2: " + value2);}private static String fetchDataFromDatabase(String key) {// 模拟从数据库中加载数据的逻辑System.out.println("Fetching data from database for key: " + key);// 返回从数据库中查询到的数据return "Value for " + key;}
}
在上面的示例中,我们创建了一个自动加载缓存,使用Caffeine.newBuilder()
方法创建一个Caffeine缓存构建器,并通过.maximumSize()
方法设置缓存的最大大小,.expireAfterWrite()
方法设置缓存条目的过期时间。然后,在.build()
方法中传递一个CacheLoader
的实现,用于定义缓存条目的加载逻辑。
在示例中,我们调用cache.get("key1")
和cache.get("key2")
来获取缓存中的数据。如果缓存中不存在某个键的条目,Caffeine会自动调用CacheLoader
的load
方法来加载该条目。在我们的示例中,fetchDataFromDatabase
方法模拟从数据库中加载数据的逻辑,并返回一个字符串作为缓存条目的值。
2. 缓存条目异步加载
Caffeine支持使用异步方式加载缓存条目。你可以通过使用AsyncCacheLoader
接口来实现异步加载逻辑,并在构建Caffeine缓存时使用.buildAsync(AsyncCacheLoader)
方法将其传递给Caffeine。这样,当缓存中不存在某个键的条目时,Caffeine会自动调用AsyncCacheLoader
的asyncLoad
方法来异步加载该条目。
以下是一个示例代码,演示如何使用Caffeine实现异步加载缓存条目:
import com.github.benmanes.caffeine.cache.AsyncCache;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;public class AsyncLoadingCacheExample {public static void main(String[] args) {Executor executor = Executors.newFixedThreadPool(5); // 自定义线程池// 创建异步加载缓存AsyncCache<String, String> cache = Caffeine.newBuilder().maximumSize(100).buildAsync((key, executor1) -> asyncFetchData(key, executor));// 异步加载缓存条目CompletableFuture<String> future = cache.get("key1", executor);// 获取加载结果future.thenAccept(value -> System.out.println("Value: " + value));}private static CompletableFuture<String> asyncFetchData(String key, Executor executor) {// 模拟异步加载数据的逻辑System.out.println("Asynchronously fetching data for key: " + key);return CompletableFuture.supplyAsync(() -> {// 模拟耗时操作try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// 返回加载的数据return "Value for " + key;}, executor);}
}
在上面的示例中,我们创建了一个异步加载缓存,使用Caffeine.newBuilder()
方法创建一个Caffeine缓存构建器,并通过.maximumSize()
方法设置缓存的最大大小。然后,在.buildAsync()
方法中传递一个AsyncCacheLoader
的实现,用于定义异步加载缓存条目的逻辑。
在示例中,我们调用cache.get("key1", executor)
来异步加载缓存条目。executor
参数用于指定异步加载操作的执行器。我们使用CompletableFuture
来处理异步加载结果,通过thenAccept
方法来接收加载的数据并进行处理。
在示例中,asyncFetchData
方法模拟异步加载数据的逻辑,并返回一个CompletableFuture
作为加载的结果。
3. 根据访问频率和新近度的剔除策略
当根据访问频率和新近度等因素来计算权重时,你可以使用Caffeine提供的FrequencySketch
和Ticker
来实现。FrequencySketch
用于跟踪条目的访问频率,而Ticker
用于获取当前时间。
以下是一个示例代码,演示如何根据访问频率和新近度等因素来计算权重:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.FrequencySketch;
import com.github.benmanes.caffeine.cache.Ticker;
import java.util.concurrent.TimeUnit;public class WeightCalculationExample {public static void main(String[] args) throws InterruptedException {int cacheSize = 100; // 缓存最大容量long expireAfterWrite = 10; // 缓存条目的写入过期时间(秒)// 创建基于容量和权重的剔除策略缓存Cache<String, Object> cache = Caffeine.newBuilder().maximumSize(cacheSize).expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS).weigher((key, value) -> computeWeight(key, value)).build();// 向缓存中放入数据cache.put("key1", "value1");cache.put("key2", "value2");cache.put("key3", "value3");// 模拟访问缓存cache.getIfPresent("key1");cache.getIfPresent("key1");cache.getIfPresent("key2");cache.getIfPresent("key3");// 输出缓存条目的权重System.out.println("Weight for key1: " + cache.policy().eviction().getWeight("key1"));System.out.println("Weight for key2: " + cache.policy().eviction().getWeight("key2"));System.out.println("Weight for key3: " + cache.policy().eviction().getWeight("key3"));// 等待过期时间后,再次输出缓存条目的权重TimeUnit.SECONDS.sleep(expireAfterWrite);System.out.println("Weight for key1 after expiration: " + cache.policy().eviction().getWeight("key1"));}private static int computeWeight(String key, Object value) {FrequencySketch<?> frequencySketch = (FrequencySketch<?>) value;long accessCount = frequencySketch.estimated();long elapsedTime = Ticker.systemTicker().read() - frequencySketch.lastAccessTime();// 计算权重:访问频率 * 新近度double frequencyWeight = Math.log(accessCount + 1);double recencyWeight = Math.exp(-elapsedTime / 1000.0); // 转换为秒return (int) (frequencyWeight * recencyWeight);}
}
在上面的示例中,我们创建了一个基于容量和权重的剔除策略缓存。我们使用Caffeine.newBuilder()
方法创建一个Caffeine缓存构建器,并通过.maximumSize()
方法设置缓存的最大容量,.expireAfterWrite()
方法设置缓存条目的写入过期时间。然后,使用.weigher()
方法设置缓存条目的权重函数,通过computeWeight
方法来计算缓存条目的权重。
在示例中,我们向缓存中放入了三个键值对,并模拟访问缓存的行为。然后,通过cache.policy().eviction().getWeight()
方法获取缓存条目的权重,并输出结果。在computeWeight
方法中,我们使用FrequencySketch
获取条目的访问频率和最近访问时间,并根据访问频率和新近度来计算权重。
4. 基于最后访问时间的剔除策略
通过Caffeine,您可以设置缓存项的过期时间,并根据最后访问或写入时点以来的时长来判断缓存项是否过期。以下是使用Caffeine执行基于时间的失效策略的示例:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;// 创建缓存实例
Cache<String, String> cache = Caffeine.newBuilder().expireAfterAccess(Duration.ofMinutes(5)) // 设置访问后的过期时间.build();// 向缓存中存储数据
cache.put("key", "value");// 获取缓存数据
String value = cache.getIfPresent("key");// 判断缓存项是否过期
if (cache.policy().eviction().get().wasEvicted()) {// 缓存项已过期,执行失效策略// ...
} else {// 缓存项仍然有效,继续使用// ...
}
在示例中,我们使用Caffeine创建了一个缓存实例,并通过expireAfterAccess()
方法设置了缓存项的访问后过期时间为5分钟。然后,我们使用put()
方法将数据存储到缓存中,并使用getIfPresent()
方法获取缓存数据。通过cache.policy().eviction().get().wasEvicted()
可以判断缓存项是否已过期。如果返回true
,则表示缓存项已过期,可以执行相应的失效策略。否则,缓存项仍然有效,可以继续使用。
5. 条目删除(移除)通知
在Caffeine中,当缓存条目被剔除(或移除)时,您可以通过使用监听器或回调来接收通知。
Caffeine提供了RemovalListener
接口,您可以实现该接口来定义在缓存条目被剔除时要执行的操作。下面是一个示例:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;class MyRemovalListener implements RemovalListener<String, String> {@Overridepublic void onRemoval(String key, String value, RemovalCause cause) {// 在缓存条目被剔除时执行的操作// ...}
}// 创建缓存实例,并设置RemovalListener
Cache<String, String> cache = Caffeine.newBuilder().removalListener(new MyRemovalListener()).build();// 向缓存中添加数据
cache.put("key", "value");// 从缓存中移除数据
cache.invalidate("key");
在示例中,我们创建了一个实现了RemovalListener
接口的自定义MyRemovalListener
类。在onRemoval()
方法中,您可以定义在缓存条目被剔除时要执行的操作。
然后,我们通过removalListener()
方法将MyRemovalListener
实例设置为缓存的剔除监听器。当调用cache.invalidate("key")
从缓存中移除数据时,该数据将被传递给MyRemovalListener
的onRemoval()
方法,从而实现在缓存条目被剔除时执行自定义的操作。
通过这种方式,您可以接收到关于缓存条目剔除的通知,并在需要时执行相应的操作。
6. 条目写入传播到外部资源
在Caffeine中,当缓存条目被写入时,您可以通过使用监听器或回调来传播该操作到外部资源。
Caffeine提供了CacheWriter
接口,您可以实现该接口来定义在缓存条目被写入时要执行的操作。下面是一个示例:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.CacheWriter;class MyCacheWriter implements CacheWriter<String, String> {@Overridepublic void write(String key, String value) {// 将缓存条目写入到外部资源(例如数据库、文件等)// ...}@Overridepublic void delete(String key, String value, RemovalCause cause) {// 在缓存条目被删除时执行的操作// ...}
}// 创建缓存实例,并设置CacheWriter
Cache<String, String> cache = Caffeine.newBuilder().writer(new MyCacheWriter()).build();// 向缓存中写入数据
cache.put("key", "value");
在示例中,我们创建了一个实现了CacheWriter
接口的自定义MyCacheWriter
类。在write()
方法中,您可以定义将缓存条目写入到外部资源的操作,例如将其存储到数据库或文件中。在delete()
方法中,您可以定义在缓存条目被删除时要执行的操作。
然后,我们通过writer()
方法将MyCacheWriter
实例设置为缓存的写入器。当调用cache.put("key", "value")
写入数据时,该数据将被传递给MyCacheWriter
的write()
方法,从而实现将缓存条目写入到外部资源的操作。
通过这种方式,您可以在缓存条目写入时执行自定义的操作,并将其传播到外部资源。
7. 缓存累积访问量统计
要在Caffeine中进行缓存累积访问量统计,可以使用Caffeine提供的统计功能。Caffeine提供了CacheStats
类,可以用于获取缓存的统计信息,包括命中次数、错过次数、加载次数等。
下面是一个示例:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.stats.CacheStats;// 创建缓存实例
Cache<String, String> cache = Caffeine.newBuilder().build();// 向缓存中添加数据
cache.put("key1", "value1");
cache.put("key2", "value2");// 获取缓存统计信息
CacheStats stats = cache.stats();// 输出统计结果
System.out.println("命中次数: " + stats.hitCount());
System.out.println("错过次数: " + stats.missCount());
System.out.println("加载次数: " + stats.loadCount());
System.out.println("加载成功次数: " + stats.loadSuccessCount());
System.out.println("加载失败次数: " + stats.loadFailureCount());
在示例中,我们创建了一个简单的缓存实例,并向其中添加了两个缓存项。然后,通过cache.stats()
方法获取缓存的统计信息,并使用CacheStats
类中的相关方法获取具体的统计数据。
您可以根据需要输出或记录这些统计数据,以便了解缓存的使用情况和性能。这些统计信息可以帮助您优化缓存策略和性能调优。
请注意,Caffeine的统计信息是近似值,不一定是精确的。如果您需要更精确的统计信息,可以考虑使用其他监控和度量工具来监视缓存的使用情况。
这篇关于Caffeine - Home的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!