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

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

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

首先创建一个缓存实体类

package com.example.vuespringboot.bean;

import lombok.Data;

/**

* @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;

/**
* @date 2023/7/27
* @des 自定义本地缓存工具类
*/

public class CacheUtil {

/**
* 缓存数据Map
*/

private static final Map 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() 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 要强大的多,支持很多特性如下:

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

1.添加gugva的依赖


com.google.guava
guava
31.1-jre

2.测试

    @Test
void testGuava() throws ExecutionException, InterruptedException {
// 创建一个缓存实例
Cache 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 是基于 java8 实现的新一代缓存工具,缓存性能接近理论最优,可以看作是 Guava Cache 的增强版,功能上两者类似,不同的是 Caffeine 采用了一种结合 LRU、LFU 优点的算法:W-TinyLFU,在性能上有明显的优越性。

1.引入Caffeine


com.github.ben-manes.caffeine
caffeine
2.9.3

2.测试

  @Test
void testCaffeine() throws InterruptedException {
// 创建一个缓存实例
Cache 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已过期

1.引入ehcache依赖


org.ehcache
ehcache
3.9.7

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;

/**
* @date 2023/7/27
* @des 自定义过期策略实现
*/

public class CustomExpiryPolicy implements ExpiryPolicy {
private final Map 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 value) {
return getExpireByKey(key);
}

@Override
public Duration getExpiryForUpdate(K key, Supplier 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 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 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实现本地缓存的几种方式

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/154793.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!