首页 > 编程语言 >Java实现本地缓存的几种方式

Java实现本地缓存的几种方式

时间:2023-08-06 23:01:59浏览次数:64  
标签:缓存 Java 本地 cache key org import public

一、概况

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

二、基于ConcurrentHashMap实现本地缓存

1.首先创建一个缓存实体类

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

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

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/8/5
 * @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());
    }
}

3.测试

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.添加依赖

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

2.测试

我们直接使用Guava中api创建缓存对象,并设置过期时间等。

@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-->
<dependency>
	<groupId>com.github.ben-manes.caffeine</groupId>
	<artifactId>caffeine</artifactId>
	<version>2.9.3</version>
</dependency>

2.测试

我们直接使用Caffeine中api创建缓存对象,并设置过期时间等。

@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-->
<dependency>
	<groupId>org.ehcache</groupId>
	<artifactId>ehcache</artifactId>
	<version>3.9.7</version>
</dependency>

2.自定义Encache过期策略

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/8/5
 * @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/8/5
 * @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

六、基于Spring Cache实现缓存

启动类上加上@EnableCaching注解

package com.example.dockerdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableJpaAuditing
@EnableTransactionManagement
@EnableScheduling
@EnableCaching
public class DockerDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DockerDemoApplication.class, args);
    }

}

业务层

package com.example.dockerdemo.service;

import com.example.dockerdemo.bean.User;
import com.example.dockerdemo.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.Cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 * @author qx
 * @date 2023/7/21
 * @des 业务层测试
 */
@Service
@Slf4j
@CacheConfig(cacheNames = "userService")
public class UserService {

    @Autowired
    private UserRepository userRepository;


    @Cacheable(key = "#id")
    public User getById(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    @CachePut(key = "#user.id")
    public User updateUser(User user) {
        userRepository.save(user);
        return user;
    }

    @CacheEvict(key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

控制层

package com.example.dockerdemo.controller;

import com.example.dockerdemo.bean.User;
import com.example.dockerdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @author qx
 * @date 2023/8/4
 * @des
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public User findById(@PathVariable("id") Long id) {
        return userService.getById(id);
    }

    @PostMapping
    public User updateUser(@RequestBody User user) {
        return userService.updateUser(user);
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable("id") Long id) {
        userService.deleteUser(id);
    }
}

测试:

第一次查询数据,打印了sql语句

Java实现本地缓存的几种方式_自定义缓存

Hibernate: select user0_.id as id1_0_0_, user0_.create_time as create_t2_0_0_, user0_.update_time as update_t3_0_0_, user0_.age as age4_0_0_, user0_.name as name5_0_0_ from t_user user0_ where user0_.id=?

我们第二次访问接口,并没有打印sql语句,没有请求数据库而是使用缓存。

更新缓存

Java实现本地缓存的几种方式_性能优化_02

再次查询数据,我们发现缓存数据已经修改成功了

Java实现本地缓存的几种方式_性能优化_03

标签:缓存,Java,本地,cache,key,org,import,public
From: https://blog.51cto.com/u_13312531/6987463

相关文章

  • windows应用程序icon缓存、查看图标、icon制作方法
    windows程序图标缓存在vs中替换c++程序的图标后,需要重新编译,但是很多情况下都不会刷新,还是看到老的图标,只能重启电脑才能看到新的图标。通过ChatGPT得到相关的回答如下:如果在Windows上更换了可执行文件(.exe)的图标,但是在图标文件已经更改的情况下仍然显示旧的图标,可能是因......
  • 异步请求返回处理之finally的用途,代码规范摸索【玩转JavaScript】
    前言最近在改动老代码时,发现了一个有趣的现象。对于异步请求返回结果处理中,使用finally做兜底处理,不同的页面并不统一,也就是有的页面使用了,有的页面没使用,没使用的页面增加了额外的失败的处理。所以finally到底要不要统一?本着代码规范化原则的思维,我准备一探究竟。文章速度finally......
  • 【JavaScript17】eval函数
    eval本身在js里面正常情况下使用的并不多.但是很多网站会利用eval的特性来完成反爬操作.我们来看看eval是个什么鬼?从功能上讲,eval非常简单.它和python里面的eval是一样的.它可以动态的把字符串当成js代码进行运行.vars="1+2+3+4+5+6+7+8";varc=eval(s);//帮你......
  • Java数组
       ......
  • 【JavaScript16】定时器
    在JS中,有两种设置定时器的方案1、setTimeout//语法规则t=setTimeout(函数,时间)//经过xxx时间后,执行xxx函数//m是第几个定时器varm=setTimeout(function(){console.log("我叫xwl");},5000);//单位是毫秒console.log("正常执行的....");......
  • 【JavaScript15】闭包
    什么是闭包闭包(closure)是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰,即形成一个不销毁的栈环境。闭包的特性:函数嵌套函数内部函数可以访问外部函数的变量参数和变量不会被回收。为什么要有闭包?1、先来看一段代码发现没有......
  • 【JavaScript14】函数基础
    函数定义函数定义的方法有多种,主要分为函数声明和函数表达式//函数声明functionfunc(arg1,arg2){console.log("arg1=",arg1);console.log("arg2=",arg2);return"返回一些东西"}varret=func("苹果","鸭梨");console.log(......
  • 异常: java.security.InvalidKeyException: Illegal key size
    问题描述importorg.apache.commons.codec.digest.DigestUtils;importorg.bouncycastle.jce.provider.BouncyCastleProvider;importjavax.crypto.Cipher;importjavax.crypto.spec.SecretKeySpec;importjava.security.Security;importjava.util.Base64;publicclass......
  • ceph高速缓存池
    一.缓存池运维1.1自定义硬盘类型cephosdcrushclasslscephosdcrushclasscreatessdcephosdcrushclasscreatesatacephosdcrushrm-device-classosd.3cephosdcrushset-device-classssdosd.31.2自定义角色管理不同类型硬盘cephosdcrushrulel......
  • JAVA面试题(一)
    问:谈谈你自己对面向对象的理解?抽象,封装,继承,多态理解:假如我是女娲,我要造人(人是个对象),人要有思想,肉体(抽象) 因为要造很多人,所以我想请人一起帮忙造,但是我又不想让他知道我的技术,所以我就把造人的技术封装起来,然后他只需要知道造什么样子的人就行了(封装)但是这样效率还是很慢,所以我就造......