Caffeine快速入门

依赖

复制代码
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.2.0</version>
</dependency>

Cache的基本api操作

Caffeine.newBuilder.build来构建Caffeine

.maximumSize()设置最大缓存数量

expireAfterWrite(10, TimeUnit.SECONDS) 设置在缓存写后的10分钟过期

expireAfterAccess(10, TimeUnit.SECONDS)设置在缓存被访问后的10分钟过期

put("key", "value");//放入数据

getIfPresent("key-key");//获取数据,存在则返回值,不存在则返回Null

get("key2", k -> "into");//获取缓存,如果不存在则放入数据

invalidate("delete");//删除指定的缓存数据

invalidateAll();//删除所有缓存数据

cleanUp();//清理已经过期或者被标记为无效的缓存项

recordStats()//开启统计

  1. CacheStats stats = cache.stats();

  2. log.info("命中次数 / 总请求次数:{}",stats.hitRate());

  3. log.info("缓存驱逐的次数:{}",stats.evictionCount());

  4. log.info("加载均值所花费的平均时间:{}",stats.averageLoadPenalty());

    @Test
    void test1() {
    Cache<String, String> cacheWrite = Caffeine.newBuilder()
    .maximumSize(100)//设置缓存的最大条目数
    .expireAfterWrite(10, TimeUnit.SECONDS)//设置在缓存写后的10分钟过期
    .build();
    Cache<String, String> cacheAccess = Caffeine.newBuilder()
    .maximumSize(100)//设置缓存的最大条目数
    .expireAfterAccess(10, TimeUnit.SECONDS)//设置在缓存被访问后的10分钟过期
    .build();

    复制代码
     cacheWrite.put("key", "value");//放入数据
     cacheWrite.getIfPresent("key-key");//获取数据,存在则返回值,不存在则返回Null
     cacheWrite.get("key2", k -> "into");//获取缓存,如果不存在则放入数据
    
     cacheWr.aite.invalidate("delete");//删除指定的缓存数据
     cacheWrite.invalidateAll();//删除所有缓存数据
     cacheWrite.cleanUp();//清理已经过期或者被标记为无效的缓存项
    
    
     //拿出统计信息
     Cache<String,String> cache = Caffeine.newBuilder()
             .maximumSize(10_000)
             .recordStats()//开启统计
             .build();
     CacheStats stats = cache.stats();
     log.info("命中次数 / 总请求次数:{}",stats.hitRate());
     log.info("缓存驱逐的次数:{}",stats.evictionCount());
     log.info("加载均值所花费的平均时间:{}",stats.averageLoadPenalty());

    }


LoadingCache的自动刷新

refreshAfterWrite(Duration.ofMinutes(1)) 写入一分钟后触发自动刷新

build(刷新方法)

复制代码
//LoadingCache支持自动加载数据
//可以在构建 LoadingCache 时指定一个 CacheLoader
//当尝试获取一个不存在的缓存项时,LoadingCache 会自动调用 CacheLoader 来加载数据,并将加载的数据存入缓存,然后返回该数据
@Test
void test2() {
    // 创建自动加载缓存
    LoadingCache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(100)
            .refreshAfterWrite(Duration.ofMinutes(1))//写入一分钟后触发自动刷新
            .build(key -> loadFromDatabase(key)); // 缓存未命中时自动调用此方法

    // 使用缓存
    String value = cache.get("user101"); // 自动加载
    System.out.println(value);
}

private static String loadFromDatabase(String key) {
    // 模拟数据库查询
    System.out.println("Loading from DB: " + key);
    return "data_for_" + key;
}

移除监听器

removalListener()

复制代码
//移除监听器
@Test
void test3() {
    // 创建移除监听器
    RemovalListener<String, String> removalListener = (key, value, cause) -> {
        System.out.printf("Key %s was removed (%s), value: %s%n", key, cause, value);
    };

    // 创建 Caffeine 缓存并设置移除监听器
    Cache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(2)
            .removalListener(removalListener)
            .build();

    // 向缓存中添加元素
    cache.put("key1", "value1");
    cache.put("key2", "value2");
    cache.put("key3", "value3");
}

写入监听器

复制代码
//写入监听器
@Test
void test4() {
    Cache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(2)
            .removalListener((String key, String value, RemovalCause cause) -> {
                // 根据 cause 执行不同逻辑
                switch (cause) {
                    case EXPLICIT:
                        System.out.printf("[手动删除] Key=%s, Value=%s%n", key, value);
                        break;
                    case SIZE:
                        System.out.printf("[容量驱逐] Key=%s (当前值可能已过时)%n", key);
                        break;
                    case EXPIRED:
                        System.out.printf("[过期失效] Key=%s%n", key);
                        break;
                    case REPLACED:
                        System.out.printf("[值被覆盖(写入)] Key=%s (旧值: %s)%n", key, value);
                        break;
                    default:
                        System.out.printf("[其他原因] Key=%s (原因: %s)%n", key, cause);
                }
            })
            .build();

    // 测试不同场景
    cache.put("k1", "v1");
    cache.put("k2", "v2");
    cache.put("k3", "v3");  // 触发 SIZE 驱逐 k1
    cache.invalidate("k2"); // 触发 EXPLICIT 删除
    cache.put("k3", "v3_new"); // 触发 REPLACED
}

定制化缓存清除策略

expireAfter()

复制代码
//定制化缓存清除策略
void test5() {
    Cache<String, String> cache = Caffeine.newBuilder()
            .maximumSize(100)
            .expireAfter(new Expiry<String, String>() {
                @Override
                public long expireAfterCreate(@NonNull String s, @NonNull String s2, long l) {
                    log.info("[创建后失效计算 key = {}, value = {}]", s, s2);
                    // 相当于创建后多少秒就失效了
                    return TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS);
                }

                @Override
                public long expireAfterUpdate(@NonNull String s, @NonNull String s2, long l, long l1) {
                    log.info("[更新后失效计算 key = {}, value = {}]", s, s2);
                    // 更新完多少秒后就失效了
                    return TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS);
                }

                @Override
                public long expireAfterRead(@NonNull String s, @NonNull String s2, long l, long l1) {
                    log.info("[读取后失效计算 key = {}, value = {}]", s, s2);
                    // 读取完多少秒后就失效了
                    return TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS);  // 将2秒转成纳秒
                }
            })   // 第一步
            .build();
    cache.put("Bob", "已登录");
    cache.put("Lily", "未登录");
    cache.put("Wang", "未登录");
    cache.put("Lee", "已登录");

    log.info("获取缓存数据,Bob= {}", cache.getIfPresent("Bob"));
    log.info("获取缓存数据,Lily= {}", cache.getIfPresent("Lily"));
    log.info("获取缓存数据,Wang= {}", cache.getIfPresent("Wang"));
    log.info("获取缓存数据,Lee= {}", cache.getIfPresent("Lee"));

}

基于权重的缓存清除策略

weigher()

复制代码
//基于权重的缓存清除策略,总权重大于配置数量时,Caffeine 会优先淘汰那些最近最少被访问的缓存项
@Test
void test6() throws InterruptedException {
    Cache<String, String> cache = Caffeine.newBuilder()
            .maximumWeight(100)
            .weigher((key, value) -> {
                log.info("[weigher权重计算器] key = {}, val = {}", key, value);
                return 50;
            })
            .expireAfterAccess(3L, TimeUnit.SECONDS)
            .build();

    // 放入第一个缓存项,总权重为 50
    cache.put("key1", "value1");
    System.out.println("放入 key1 后,缓存大小: " + cache.estimatedSize());

    // 放入第二个缓存项,总权重为 100
    cache.put("key2", "value2");
    System.out.println("放入 key2 后,缓存大小: " + cache.estimatedSize());

    // 放入第三个缓存项,总权重大于 100,会触发淘汰
    cache.put("key3", "value3");
    System.out.println("放入 key3 后,缓存大小: " + cache.estimatedSize());

    // 等待 3 秒,让缓存项过期
    Thread.sleep(3000);
    cache.cleanUp();
    System.out.println("等待 3 秒后,缓存大小: " + cache.estimatedSize());
}

测试类源码

复制代码
package com.example.kiratest.test;

import com.github.benmanes.caffeine.cache.*;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import io.micrometer.common.lang.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

@SpringBootTest
@Slf4j
public class CaffeineTest {

    @Test
    void test1() {
        Cache<String, String> cacheWrite = Caffeine.newBuilder()
                .maximumSize(100)//设置缓存的最大条目数
                .expireAfterWrite(10, TimeUnit.SECONDS)//设置在缓存写后的10分钟过期
                .build();
        Cache<String, String> cacheAccess = Caffeine.newBuilder()
                .maximumSize(100)//设置缓存的最大条目数
                .expireAfterAccess(10, TimeUnit.SECONDS)//设置在缓存被访问后的10分钟过期
                .build();

        cacheWrite.put("key", "value");//放入数据
        cacheWrite.getIfPresent("key-key");//获取数据,存在则返回值,不存在则返回Null
        cacheWrite.get("key2", k -> "into");//获取缓存,如果不存在则放入数据

        cacheWrite.invalidate("delete");//删除指定的缓存数据
        cacheWrite.invalidateAll();//删除所有缓存数据
        cacheWrite.cleanUp();//清理已经过期或者被标记为无效的缓存项


        //拿出统计信息
        Cache<String,String> cache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .recordStats()//开启统计
                .build();
        CacheStats stats = cache.stats();
        log.info("命中次数 / 总请求次数:{}",stats.hitRate());
        log.info("缓存驱逐的次数:{}",stats.evictionCount());
        log.info("加载均值所花费的平均时间:{}",stats.averageLoadPenalty());


    }


    //LoadingCache支持自动加载数据
    //可以在构建 LoadingCache 时指定一个 CacheLoader
    //当尝试获取一个不存在的缓存项时,LoadingCache 会自动调用 CacheLoader 来加载数据,并将加载的数据存入缓存,然后返回该数据
    @Test
    void test2() {
        // 创建自动加载缓存
        LoadingCache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(100)
                .refreshAfterWrite(Duration.ofMinutes(1))//写入一分钟后触发自动刷新
                .build(key -> loadFromDatabase(key)); // 缓存未命中时自动调用此方法

        // 使用缓存
        String value = cache.get("user101"); // 自动加载
        System.out.println(value);
    }

    private static String loadFromDatabase(String key) {
        // 模拟数据库查询
        System.out.println("Loading from DB: " + key);
        return "data_for_" + key;
    }


    //移除监听器
    @Test
    void test3() {
        // 创建移除监听器
        RemovalListener<String, String> removalListener = (key, value, cause) -> {
            System.out.printf("Key %s was removed (%s), value: %s%n", key, cause, value);
        };

        // 创建 Caffeine 缓存并设置移除监听器
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(2)
                .removalListener(removalListener)
                .build();

        // 向缓存中添加元素
        cache.put("key1", "value1");
        cache.put("key2", "value2");
        cache.put("key3", "value3");
    }

    //写入监听器
    @Test
    void test4() {
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(2)
                .removalListener((String key, String value, RemovalCause cause) -> {
                    // 根据 cause 执行不同逻辑
                    switch (cause) {
                        case EXPLICIT:
                            System.out.printf("[手动删除] Key=%s, Value=%s%n", key, value);
                            break;
                        case SIZE:
                            System.out.printf("[容量驱逐] Key=%s (当前值可能已过时)%n", key);
                            break;
                        case EXPIRED:
                            System.out.printf("[过期失效] Key=%s%n", key);
                            break;
                        case REPLACED:
                            System.out.printf("[值被覆盖(写入)] Key=%s (旧值: %s)%n", key, value);
                            break;
                        default:
                            System.out.printf("[其他原因] Key=%s (原因: %s)%n", key, cause);
                    }
                })
                .build();

        // 测试不同场景
        cache.put("k1", "v1");
        cache.put("k2", "v2");
        cache.put("k3", "v3");  // 触发 SIZE 驱逐 k1
        cache.invalidate("k2"); // 触发 EXPLICIT 删除
        cache.put("k3", "v3_new"); // 触发 REPLACED
    }

    //定制化缓存清除策略
    void test5() {
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumSize(100)
                .expireAfter(new Expiry<String, String>() {
                    @Override
                    public long expireAfterCreate(@NonNull String s, @NonNull String s2, long l) {
                        log.info("[创建后失效计算 key = {}, value = {}]", s, s2);
                        // 相当于创建后多少秒就失效了
                        return TimeUnit.NANOSECONDS.convert(2, TimeUnit.SECONDS);
                    }

                    @Override
                    public long expireAfterUpdate(@NonNull String s, @NonNull String s2, long l, long l1) {
                        log.info("[更新后失效计算 key = {}, value = {}]", s, s2);
                        // 更新完多少秒后就失效了
                        return TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS);
                    }

                    @Override
                    public long expireAfterRead(@NonNull String s, @NonNull String s2, long l, long l1) {
                        log.info("[读取后失效计算 key = {}, value = {}]", s, s2);
                        // 读取完多少秒后就失效了
                        return TimeUnit.NANOSECONDS.convert(5, TimeUnit.SECONDS);  // 将2秒转成纳秒
                    }
                })   // 第一步
                .build();
        cache.put("Bob", "已登录");
        cache.put("Lily", "未登录");
        cache.put("Wang", "未登录");
        cache.put("Lee", "已登录");

        log.info("获取缓存数据,Bob= {}", cache.getIfPresent("Bob"));
        log.info("获取缓存数据,Lily= {}", cache.getIfPresent("Lily"));
        log.info("获取缓存数据,Wang= {}", cache.getIfPresent("Wang"));
        log.info("获取缓存数据,Lee= {}", cache.getIfPresent("Lee"));

    }


    //基于权重的缓存清除策略,总权重大于配置数量时,Caffeine 会优先淘汰那些最近最少被访问的缓存项
    @Test
    void test6() throws InterruptedException {
        Cache<String, String> cache = Caffeine.newBuilder()
                .maximumWeight(100)
                .weigher((key, value) -> {
                    log.info("[weigher权重计算器] key = {}, val = {}", key, value);
                    return 50;
                })
                .expireAfterAccess(3L, TimeUnit.SECONDS)
                .build();

        // 放入第一个缓存项,总权重为 50
        cache.put("key1", "value1");
        System.out.println("放入 key1 后,缓存大小: " + cache.estimatedSize());

        // 放入第二个缓存项,总权重为 100
        cache.put("key2", "value2");
        System.out.println("放入 key2 后,缓存大小: " + cache.estimatedSize());

        // 放入第三个缓存项,总权重大于 100,会触发淘汰
        cache.put("key3", "value3");
        System.out.println("放入 key3 后,缓存大小: " + cache.estimatedSize());

        // 等待 3 秒,让缓存项过期
        Thread.sleep(3000);
        cache.cleanUp();
        System.out.println("等待 3 秒后,缓存大小: " + cache.estimatedSize());
    }



}
相关推荐
冼紫菜4 分钟前
Spring 项目无法连接 MySQL:Nacos 配置误区排查与解决
java·spring boot·后端·mysql·docker·springcloud
Aliano21719 分钟前
Pinecone向量库 VS Redis
数据库·redis·缓存·pinecone向量库
诸葛小猿30 分钟前
Pdf转Word案例(java)
java·pdf·word·格式转换
yuren_xia35 分钟前
Spring MVC中跨域问题处理
java·spring·mvc
计算机毕设定制辅导-无忧学长44 分钟前
ActiveMQ 源码剖析:消息存储与通信协议实现(二)
java·activemq·java-activemq
大G哥1 小时前
用 Go 和 TensorFlow 实现图像验证码识别系统
开发语言·后端·golang·tensorflow·neo4j
一个憨憨coder1 小时前
Spring 如何解决循环依赖问题?
java·后端·spring
钢铁男儿1 小时前
深入解析C#参数传递:值参数 vs 引用参数
java·开发语言·c#
代码哈士奇1 小时前
认识中间件-以及两个简单的示例
后端·中间件·typescript·nodejs·nest
学渣676561 小时前
.idea和__pycache__文件夹分别是什么意思
java·ide·intellij-idea