Caffeine - Home

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会自动调用CacheLoaderload方法来加载该条目。

以下是一个示例代码,演示如何使用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会自动调用CacheLoaderload方法来加载该条目。在我们的示例中,fetchDataFromDatabase方法模拟从数据库中加载数据的逻辑,并返回一个字符串作为缓存条目的值。

2. 缓存条目异步加载

Caffeine支持使用异步方式加载缓存条目。你可以通过使用AsyncCacheLoader接口来实现异步加载逻辑,并在构建Caffeine缓存时使用.buildAsync(AsyncCacheLoader)方法将其传递给Caffeine。这样,当缓存中不存在某个键的条目时,Caffeine会自动调用AsyncCacheLoaderasyncLoad方法来异步加载该条目。

以下是一个示例代码,演示如何使用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提供的FrequencySketchTicker来实现。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> {
    @Override
    public 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")从缓存中移除数据时,该数据将被传递给MyRemovalListeneronRemoval()方法,从而实现在缓存条目被剔除时执行自定义的操作。

通过这种方式,您可以接收到关于缓存条目剔除的通知,并在需要时执行相应的操作。

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> {
    @Override
    public void write(String key, String value) {
        // 将缓存条目写入到外部资源(例如数据库、文件等)
        // ...
    }

    @Override
    public 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")写入数据时,该数据将被传递给MyCacheWriterwrite()方法,从而实现将缓存条目写入到外部资源的操作。

通过这种方式,您可以在缓存条目写入时执行自定义的操作,并将其传播到外部资源。

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的官方文档包含了详细的API文档,可以帮助用户了解Caffeine的使用方法和功能。其中包括翻译后的API文档caffeine-2.8.0-javadoc-API文档-中文(简体)版.zip,用户可以通过该文档查阅相关的类和方法的说明。此外,用户还可以通过Maven坐标com.github.ben-manes.caffeine:caffeine:2.8.0来引入Caffeine的jar包。 官方文档中也提到了caffeine内部采用ConcurrentHashMap进行缓存数据,并且提供了Scheduler机制来定时清除淘汰缓存,同时还有Executor来执行异步查询任务并存入缓存。 用户可以通过官方文档来了解更多关于Caffeine的细节和使用方法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [缓存性能之王caffeine使用文档](https://blog.csdn.net/lifan_zuishuai/article/details/122894838)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [caffeine-2.8.0-API文档-中文版.zip](https://download.csdn.net/download/qq_36462452/86089932)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值