Caffeine cache实现本地缓存(简单又清楚)

    科技2025-06-14  18

    Caffeine cache实现本地缓存题

    缓存填充策略 手动加载 介绍: 使用方式:  同步加载 介绍: 使用方式:  异步加载 介绍:  注意: 异步和同步使用方式相似, 这里的话主要写一下创建和异步转同步 使用方式: 过期策略 基于大小过期  介绍:当缓存超出后,使用W-TinyLFU算法进行缓存淘汰处理  使用方式:  基于权重过期  介绍:  使用方式:  基于时间过期  介绍:  使用方式:  基于引用回收  介绍:  使用方式: 基本使用 手动删除  使用方式:  自动刷新  使用方式:  移除通知  使用方式:  外部存储  使用方式:  统计缓存使用情况  使用方式: 实例整合 编码方式  使用方式:  注解方式(整合SpringBoot的cache)  介绍:  使用方式:   @Cacheable注解:   @CachePut注解:   @CacheEvict注解:   @CacheConfig注解:   @Caching注解: 简易缓存工具类

    缓存填充策略

    介绍:缓存的填充方式有三种,手动、同步和异步

     手动加载

     介绍:

    手动控制缓存的增删改处理,主动增加、获取以及依据函数式更新缓存,底层使用ConcurrentHashMap进行节点存储,因此get方法是安全的。批量查找可以使getAllPresent()方法或者带填充默认值的getAll()方法

     使用方式:

    @Test void test1() { com.github.benmanes.caffeine.cache.@NonNull Cache<String, String> cache = Caffeine.newBuilder() // 最大数量 .maximumSize(100) // 设置缓存策略在1天未写入过期缓存 .expireAfterAccess(1, TimeUnit.DAYS) .build(); // 存入缓存 cache.put("cl", "cl"); // 根据键值获取缓存 System.out.println(cache.getIfPresent("cl")); // 根据键值获取缓存, 如果为空则调用后面的参是 System.out.println(cache.get("abc", this::buildLoader)); // 根据键值清除缓存 cache.invalidate("abc"); System.out.println(cache.getIfPresent("abc")); } // 缓存后面执行的方法 String buildLoader(String k) { return k + "+default"; }

     同步加载

     介绍:

    LoadingCache对象进行缓存的操作,使用CacheLoader进行缓存存储管理。 批量查找可以使用getAll()方法。 默认情况下, getAll()将会对缓存中没有值的key分别调用CacheLoader.load方法来构建缓存的值(build中的表达式)。我们可以重写CacheLoader.loadAll方法来提高getAll()的效率。

     使用方式:

    @Test void test1() { LoadingCache<String, String> loadingCache = Caffeine.newBuilder() .maximumSize(100) .expireAfterAccess(1, TimeUnit.DAYS) // getAll将会对缓存中, 没有值的key分别调用CacheLoader.load方法来构建缓存的值 .build(this :: buildLoader); // 存储要添加的缓存 List<String> keys = new ArrayList<>(); keys.add("cl"); keys.add("b"); keys.add("c"); keys.add("d"); // 返回一个map Map<String, String> cacheAll = loadingCache.getAll(keys); System.out.println(cacheAll.get("b")); System.out.println(cacheAll.get("c")); System.out.println(cacheAll.get("a")); } // 缓存后面执行的方法 String buildLoader(String k) { return k + "+default"; }

     异步加载

     介绍:

    AsyncLoadingCache对象进行缓存管理,get()返回一个CompletableFuture对象,默认使用ForkJoinPool.commonPool()来执行异步线程,但是我们可以通过Caffeine.executor(Executor) 方法来替换线程池

      注意: 异步和同步使用方式相似, 这里的话主要写一下创建和异步转同步

     使用方式:

    @Test void test1() { AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder() .maximumSize(100) .expireAfterAccess(1,TimeUnit.DAYS) .buildAsync(k-> this.buildLoaderAsync(k).get()); try { System.out.println(asyncLoadingCache.get("123").get()); } catch (Exception e) { e.printStackTrace(); } } // 类似同步的load CompletableFuture<String> buildLoaderAsync(String k) { return CompletableFuture.supplyAsync(() -> k + "buildLoaderAsync"); } 转换使用方式: AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder() .maximumSize(100) .expireAfterAccess(1,TimeUnit.DAYS) .buildAsync(k -> this.buildLoaderAsync(k).get()); // 异步转同步 LoadingCache<String, String> cache = asyncLoadingCache.synchronous(); System.out.println("------------"); System.out.println(cache.get("dd"));

    过期策略

    介绍:

    Caffeine的缓存清除是惰性的,可能发生在读请求后或者写请求后 比如说有一条数据过期后,不会立即删除,可能在下一次读/写操作后触发删除(类比于redis的惰性删除)。 如果读请求和写请求比较少,但想要尽快的删掉cache中过期的数据的话, 可以通过增加定时器的方法,定时执行cache.cleanUp()方法(异步方法,可以等待执行),触发缓存清除操作。

     基于大小过期

      介绍:当缓存超出后,使用W-TinyLFU算法进行缓存淘汰处理

      使用方式:

    maximumSize()方法,参数是缓存中存储的最大缓存条目,当添加缓存时达到条目阈值后,将进行缓存淘汰操作 // 使用方式 AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder() .maximumSize(100)

     基于权重过期

      介绍:

    通过权重来计算,每个实体都有不同的权重,总权重到达最高时淘汰实体。 weigher()方法可以指定缓存所占比重,maximumWeight()方法指定最大的权重阈值,当添加缓存超过规定权重后,进行数据淘汰

      使用方式:

    com.github.benmanes.caffeine.cache.@NonNull LoadingCache<String, String> cache = Caffeine.newBuilder() .maximumWeight(10) .weigher(new Weigher<Object, Object>() { @Override public @NonNegative int weigh(@NonNull Object o, @NonNull Object o2) { System.out.println("key" + o + "value" + o2); return 5; } }) .build(this::buildLoader); List<String> list = Lists.newArrayList("c1", "c2", "c3"); cache.put(list.get(0), list.get(0)); System.out.println(list.get(0) + "--" + cache.get(list.get(0))); cache.put(list.get(1), list.get(1)); System.out.println(list.get(1) + "---" + cache.get(list.get(1))); System.out.println(cache.getAll(list));

     基于时间过期

      介绍:

    expireAfterAccess():缓存访问后,一定时间失效;即最后一次访问或者写入开始时计时。 expireAfterWrite():缓存写入后,一定时间失效;以写入缓存操作为准计时。(在最后一次写入缓存后开始计时,在指定的时间后过期。) expireAfter():自定义缓存策略,满足多样化的过期时间要求。 这里只展示after, 其他的话 使用方式大致相同,自己动手试试 注意:当expireAfterAccess和expireAfterWrite同时存在时,只有expireAfterWrite有效

      使用方式:

    com.github.benmanes.caffeine.cache.@NonNull LoadingCache<String, String> cache = Caffeine.newBuilder() .expireAfter(new Expiry<Object, Object>() { // 创建后过期 @Override public long expireAfterCreate(@NonNull Object o, @NonNull Object o2, long l) { return 1; } // 更新后过期 @Override public long expireAfterUpdate(@NonNull Object o, @NonNull Object o2, long l, @NonNegative long l1) { return 1; } // 读取后过期 @Override public long expireAfterRead(@NonNull Object o, @NonNull Object o2, long l, @NonNegative long l1) { return 1; } }).build(this::buildLoader);

     基于引用回收

      介绍:

      使用方式:

    基本使用

     手动删除

      使用方式:

     自动刷新

      使用方式:

    refreshAfterWrite:这里设置的是1分钟后自动刷新

     移除通知

      使用方式:

     外部存储

      使用方式:

     统计缓存使用情况

      使用方式:

    实例整合

     编码方式

      使用方式:

    1、缓存配置类

    @Configuration public class CacheConfig { @Bean public Cache<String, Object> caffeineCache() { return Caffeine.newBuilder() // 设置最后一次写入或访问后经过固定时间过期 .expireAfterWrite(60, TimeUnit.SECONDS) // 初始的缓存空间大小 .initialCapacity(100) // 缓存的最大条数 .maximumSize(1000) .build(); } }

    2、使用 注意:这里我只写了存入的写法, 如果要获取缓存,用cache的get缓存我们存储的key就可以了。 如:

    // 以下代码的具体含义请看文章, 文章都有介绍,如果没有 可以评论 我会给你进行解答 cache.put(1,"测试缓存") // 获取缓存 cache.get("1"); // 如果你使用get爆红, 那可能是因为你没有填写第二个,那么你可以这样做 cache.get("1", value -> value); // 或者 cache.asMap().get("1");

    示例:

    private final Cache<Object, Object> cache; public FeedbackServiceImpl( Cache<Object, Object> cache) { this.cache = cache; } public void insertFeedBack(Feedback feedback) { int i = feedbackMapper.insert(feedback); cache.put(feedback.getId(), feedback); }

    注意:如果你只是简单的使用, 你可以使用末文的缓存工具类

     注解方式(整合SpringBoot的cache)

      介绍:

      使用方式:

       @Cacheable注解:

       @CachePut注解:

       @CacheEvict注解:

       @CacheConfig注解:
    // 一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用 @CacheConfig(cacheNames="emp") //抽取缓存的公共配置
       @Caching注解:

    示例:

    // 开启缓存 @EnableCaching public class Springboot01CacheApplication { public static void main(String[] args) { SpringApplication.run(Springboot01CacheApplication.class, args); } } // 其他方法使用和他一样, 具体可以看代码,来自己动动手试试看 @Cacheable(cacheNames = {"emp"},condition = "#id>0") public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee emp = employeeMapper.getEmpById(id); return emp; }

    简易缓存工具类

     具体含义看代码注释

    /** * @author huangye 所谓致知在格物者,言欲致吾之知,在即物而穷其理也。 * @version 1.0 2020/10/4 */ public class CacheUtil{ private static Cache<String, Object> cache; static { cache = createCache(); } /** * 插入缓存 | 更新缓存 * @param cacheName 缓存前缀 * @param key 键 * @param value 值 */ public static void saveCache(String cacheName, String key, Object value) { System.out.printf("存储缓存的数据 key(%s) value(%s)",cacheName + "-" + key, value); System.out.println(); cache.asMap().put(cacheName + "-" + key, value); } /** * 根据键获取缓存 * @param cacheName 缓存前缀 * @param key 键 * @return Object类型, 由使用者自己转换 */ public static Object getCache(String cacheName, String key) { System.out.printf("获取缓存的数据 key(%s) value(%s)",cacheName + "-" + key, cache.asMap().get(cacheName + "-" + key) ); System.out.println(); return cache.asMap().get(cacheName + "-" + key); } /** * 根据键值删除缓存 * @param cacheName 缓存前缀 * @param key 建 */ public static void deleteCache(String cacheName, String key) { System.out.printf("删除缓存的数据 key(%s)", cacheName + "-" + key); System.out.println(); cache.asMap().remove(cacheName + "-" + key); } private static class CacheSingletonHolder { private final static Cache<String, Object> CACHE = Caffeine.newBuilder() // 初始的缓存空间大小 .initialCapacity(100) // 缓存的最大条数 .maximumSize(1000) // 最后一次缓存访问过后,7天后失效 .expireAfterAccess(7, TimeUnit.DAYS) .build(); } private static Cache<String, Object> createCache() { return CacheSingletonHolder.CACHE; } }

    ps:这里的图片都来源于我学习时做的笔记(下图位证), 因为有的是在网上找文章学习的,所以可能会遇到有的是和别人一样的内容,我是想着吧那些之前看的文章放到文章末尾的,但是我找不到了非常抱歉, 我这里的话只是想做个知识的汇总,如果有错误的地方,可以评论留言 我会改正,谢谢

    Processed: 0.010, SQL: 8