Java实现本地缓存的方式汇总

 更新时间:2023年07月28日 08:58:11   作者:qinxun2008081  
引入缓存,主要用于实现系统的高性能,高并发,这篇文章主要介绍了Java实现本地缓存的几种方式,本文结合示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、概念

引入缓存,主要用于实现系统的高性能,高并发。将数据库查询出来的数据放入缓存服务中,因为缓存是存储在内存中的,内存的读写性能远超磁盘的读写性能,所以访问的速度非常快。但是电脑重启后,内存中的数据会全部清除,而磁盘中的数据虽然读写性能很差,但是数据不会丢失。

二、手写本地缓存

首先创建一个缓存实体类

package com.example.vuespringboot.bean;
import lombok.Data;
/**
 * @author qx
 * @date 2023/7/27
 * @des 自定义缓存实体类
 */
@Data
public class MyCache {
    /**
     * 键
     */
    private String key;
    /**
     * 值
     */
    private Object value;
    /**
     * 过期时间
     */
    private Long expireTime;
}

接着我们编写一个缓存操作的工具类

package com.example.vuespringboot.util;
import com.example.vuespringboot.bean.MyCache;
import java.time.Duration;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * @author qx
 * @date 2023/7/27
 * @des 自定义本地缓存工具类
 */
public class CacheUtil {
    /**
     * 缓存数据Map
     */
    private static final Map<String, MyCache> CACHE_MAP = new ConcurrentHashMap<>();
    /**
     * 定时器线程池,用于清除过期缓存
     */
    private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    static {
        // 注册一个定时线程任务,服务启动1秒之后,每隔500毫秒执行一次
        // 定时清理过期缓存
        executorService.scheduleAtFixedRate(CacheUtil::clearCache, 1000, 500, TimeUnit.MILLISECONDS);
    }
    /**
     * 添加缓存
     *
     * @param key    缓存键
     * @param value  缓存值
     * @param expire 过期时间,单位秒
     */
    public static void put(String key, Object value, long expire) {
        MyCache myCache = new MyCache();
        myCache.setKey(key);
        myCache.setValue(value);
        if (expire > 0) {
            long expireTime = System.currentTimeMillis() + Duration.ofSeconds(expire).toMillis();
            myCache.setExpireTime(expireTime);
        }
        CACHE_MAP.put(key, myCache);
    }
    /**
     * 获取缓存
     *
     * @param key 缓存键
     * @return 缓存数据
     */
    public static Object get(String key) {
        if (CACHE_MAP.containsKey(key)) {
            return CACHE_MAP.get(key).getValue();
        }
        return null;
    }
    /**
     * 移除缓存
     *
     * @param key 缓存键
     */
    public static void remove(String key) {
        CACHE_MAP.remove(key);
    }
    /**
     * 清理过期的缓存数据
     */
    private static void clearCache() {
        if (CACHE_MAP.size() <= 0) {
            return;
        }
        // 判断是否过期 过期就从缓存Map删除这个元素
        CACHE_MAP.entrySet().removeIf(entry -> entry.getValue().getExpireTime() != null && entry.getValue().getExpireTime() > System.currentTimeMillis());
    }
}

最后,我们来测试一下缓存服务

package com.example.vuespringboot;
import com.example.vuespringboot.util.CacheUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
@SpringBootTest
class VueSpringBootApplicationTests {
    @Test
    void contextLoads() throws InterruptedException {
        // 写入缓存数据 2秒后过期
        CacheUtil.put("name", "qx", 2);
        Object value1 = CacheUtil.get("name");
        System.out.println("第一次查询结果:" + value1);
        // 停顿3秒
        TimeUnit.SECONDS.sleep(3);
        Object value2 = CacheUtil.get("name");
        System.out.println("第二次查询结果:" + value2);
    }
}

启动测试,我们从控制台的返回看到输出结果和我们的预期一致!

第一次查询结果:qx
第二次查询结果:null

实现思路其实很简单,采用ConcurrentHashMap作为缓存数据存储服务,然后开启一个定时调度,每隔500毫秒检查一下过期的缓存数据,然后清除掉!

三、基于Guava Cache实现本地缓存

相比自己编写的缓存服务,Guava Cache 要强大的多,支持很多特性如下:

  • 支持最大容量限制
  • 支持两种过期删除策略(插入时间和读取时间)
  • 支持简单的统计功能
  • 基于 LRU 算法实现

1.添加gugva的依赖

 <!--guava-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.1-jre</version>
        </dependency>

2.测试

    @Test
    void testGuava() throws ExecutionException, InterruptedException {
        // 创建一个缓存实例
        Cache<String, String> cache = CacheBuilder.newBuilder()
                // 初始容量
                .initialCapacity(5)
                // 最大缓存数,超出淘汰
                .maximumSize(10)
                // 过期时间 设置写入3秒后过期
                .expireAfterWrite(3, TimeUnit.SECONDS)
                .build();
        // 写入缓存数据
        cache.put("name", "qq");
        // 读取缓存数据
        String value1 = cache.get("name", () -> "key过期");
        System.out.println("第一次查询结果:" + value1);
        // 停顿4秒
        TimeUnit.SECONDS.sleep(4);
        // 读取缓存数据
        String value2 = cache.get("name", () -> "key过期");
        System.out.println("第二次查询结果:" + value2);
    }

启动测试,我们从控制台的返回看到输出结果和我们的预期一致!

第一次查询结果:qq
第二次查询结果:key过期

四、基于 Caffeine 实现本地缓存

Caffeine 是基于 java8 实现的新一代缓存工具,缓存性能接近理论最优,可以看作是 Guava Cache 的增强版,功能上两者类似,不同的是 Caffeine 采用了一种结合 LRU、LFU 优点的算法:W-TinyLFU,在性能上有明显的优越性。

1.引入Caffeine

   <!--caffeine-->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>2.9.3</version>
        </dependency>

2.测试

  @Test
    void testCaffeine() throws InterruptedException {
        // 创建一个缓存实例
        Cache<String, String> cache = Caffeine.newBuilder()
                // 初始容量
                .initialCapacity(5)
                // 最大缓存数,超出淘汰
                .maximumSize(10)
                // 设置缓存写入间隔多久过期
                .expireAfterWrite(3, TimeUnit.SECONDS)
                // 设置缓存最后访问后间隔多久淘汰,实际很少用到
                .build();
        // 写入缓存数据
        cache.put("userName", "张三");
        // 读取缓存数据
        String value1 = cache.get("userName", (key) -> {
            // 如果key不存在,会执行回调方法
            return "key已过期";
        });
        System.out.println("第一次查询结果:" + value1);
        // 停顿4秒
        Thread.sleep(4000);
        // 读取缓存数据
        String value2 = cache.get("userName", (key) -> {
            // 如果key不存在,会执行回调方法
            return "key已过期";
        });
        System.out.println("第二次查询结果:" + value2);
    }

输出结果:

第一次查询结果:张三
第二次查询结果:key已过期

五、基于 Encache 实现本地缓存

1.引入ehcache依赖

 <!--ehcache-->
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.9.7</version>
        </dependency>

2.自定义过期策略实现

package com.example.vuespringboot.util;
import org.ehcache.expiry.ExpiryPolicy;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
/**
 * @author qx
 * @date 2023/7/27
 * @des 自定义过期策略实现
 */
public class CustomExpiryPolicy<K, V> implements ExpiryPolicy<K, V> {
    private final Map<K, Duration> keyExpireMap = new ConcurrentHashMap();
    public Duration setExpire(K key, Duration duration) {
        return keyExpireMap.put(key, duration);
    }
    public Duration getExpireByKey(K key) {
        return Optional.ofNullable(keyExpireMap.get(key))
                .orElse(null);
    }
    public Duration removeExpire(K key) {
        return keyExpireMap.remove(key);
    }
    @Override
    public Duration getExpiryForCreation(K key, V value) {
        return Optional.ofNullable(getExpireByKey(key))
                .orElse(Duration.ofNanos(Long.MAX_VALUE));
    }
    @Override
    public Duration getExpiryForAccess(K key, Supplier<? extends V> value) {
        return getExpireByKey(key);
    }
    @Override
    public Duration getExpiryForUpdate(K key, Supplier<? extends V> oldValue, V newValue) {
        return getExpireByKey(key);
    }
}

3.测试

package com.example.vuespringboot.util;
import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import java.time.Duration;
/**
 * @author qx
 * @date 2023/7/27
 * @des 测试Encache
 */
public class EncacheTest {
    public static void main(String[] args) throws InterruptedException {
        String userCache = "userCache";
        // 自定义过期策略
        CustomExpiryPolicy<Object, Object> customExpiryPolicy = new CustomExpiryPolicy<>();
        // 声明一个容量为20的堆内缓存配置
        CacheConfigurationBuilder configurationBuilder = CacheConfigurationBuilder
                .newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(20))
                .withExpiry(customExpiryPolicy);
        // 初始化一个缓存管理器
        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                // 创建cache实例
                .withCache(userCache, configurationBuilder)
                .build(true);
        // 获取cache实例
        Cache<String, String> cache = cacheManager.getCache(userCache, String.class, String.class);
        // 获取过期策略
        CustomExpiryPolicy expiryPolicy = (CustomExpiryPolicy) cache.getRuntimeConfiguration().getExpiryPolicy();
        // 写入缓存数据
        cache.put("userName", "张三");
        // 设置3秒过期
        expiryPolicy.setExpire("userName", Duration.ofSeconds(3));
        // 读取缓存数据
        String value1 = cache.get("userName");
        System.out.println("第一次查询结果:" + value1);
        // 停顿4秒
        Thread.sleep(4000);
        // 读取缓存数据
        String value2 = cache.get("userName");
        System.out.println("第二次查询结果:" + value2);
    }
}

输出结果:

第一次查询结果:张三
第二次查询结果:null

到此这篇关于Java实现本地缓存的几种方式的文章就介绍到这了,更多相关java本地缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 初识JAVA数组

    初识JAVA数组

    java语言中,数组是一种最简单的复合数据类型。数组是有序数据的集合,数组中的每个元素具有相同的数据类型,可以用一个统一的数组名和下标来唯一地确定数组中的元素。数组有一维数组和多维数组。
    2014-08-08
  • mybatis-plus多表关联查询功能的实现

    mybatis-plus多表关联查询功能的实现

    本文给大家介绍mybatis-plus多表关联查询功能的实现代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2021-11-11
  • Java线程间协作wait、notify和notifyAll详解

    Java线程间协作wait、notify和notifyAll详解

    这篇文章主要介绍了Java线程间协作wait、notify和notifyAll详解,在 Java 中可以用 wait、notify 和 notifyAll 来实现线程间的通信,尽管关于wait和notify的概念很基础,它们也都是Object类的函数,但用它们来写代码却并不简单,,需要的朋友可以参考下
    2023-10-10
  • Spring Data JPA 设置字段默认值方式

    Spring Data JPA 设置字段默认值方式

    这篇文章主要介绍了Spring Data JPA设置字段默认值方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java多线程Semaphore工具的使用详解

    Java多线程Semaphore工具的使用详解

    Semaphore 是一种用于控制线程并发访问数的同步工具。它通过维护一定数量的许可证来限制对共享资源的访问,许可证的数量就是可以同时访问共享资源的线程数目,需要的朋友可以参考下
    2023-05-05
  • Java中处理日期时间的几种操作技巧

    Java中处理日期时间的几种操作技巧

    日期和时间是几乎所有应用程序中都会遇到的问题,从简单的显示当前时间,到复杂的日期计算和时区处理,日期时间操作是开发中不可避免的一部分,Java提供了不同的工具来帮助我们处理日期和时间,本文为大家详细说说,需要的朋友可以参考下
    2026-01-01
  • 基于springboot与axios的整合问题

    基于springboot与axios的整合问题

    这篇文章主要介绍了springboot与axios的整合问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java中ArrayList和LinkedList的区别

    Java中ArrayList和LinkedList的区别

    ArrayList和LinkedList在这个方法上存在一定的性能差异,本文就介绍了Java中ArrayList和LinkedList的区别,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • mybatis-sqlserver批量新增返回id方式

    mybatis-sqlserver批量新增返回id方式

    这篇文章主要介绍了mybatis-sqlserver批量新增返回id方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • spring boot mybatis日志输出到控制台的方法实践

    spring boot mybatis日志输出到控制台的方法实践

    在开发过程中我们往往需要打印出SQL语句,这样就方便我们监控问题,本文主要介绍了spring boot mybatis日志输出到控制台的方法实践,具有一定的参考价值,感兴趣的可以了解一下
    2024-05-05

最新评论