Java 实现缓存的三种方式及问题汇总
作者:枫飘长安
Java 实现缓存的三种方式
一、HashMap实现缓存
可以实现简单的本地缓存,但是实际开发中不推荐,我们可以简单模拟一下缓存的实现,
Step-1:实现一个缓存管理类
public class LocalCache {
public static HashMap<String,String> cache = new HashMap<>();
static {
String name = UUID.randomUUID().toString();
LocalCache.cache.put(String.valueOf(1),name);
System.out.println("id为1的数据添加到了缓存");
}
}
// 类中有 `static` 修饰的静态代码块,当类被加载的时候就会执行,如有不懂的可以如下博客
// https://blog.csdn.net/weixin_62636014/article/details/136851287
Tips:我们在static中完成了对缓存的初始化,你可以往缓存里面放入初始数据。
Step-2:将缓存管理类交给 Spring 进行管理
@Component
public class LocalCache {
public static HashMap<String,String> cache = new HashMap<>();
static {
String name = UUID.randomUUID().toString();
LocalCache.cache.put(String.valueOf(1),name);
System.out.println("id为1的数据添加到了缓存");
}
@PostConstruct
public void init() {
String name = UUID.randomUUID().toString();
LocalCache.cache.put(String.valueOf(2),name);
System.out.println("id为2的数据添加到了缓存");
}
}
Tips:在将缓存管理类交给了Spring进行管理后,在方法上加入@PostConstruct,可以使方法默认执行,注意该注解不是Spring框架提供,仅仅是由Java JDK提供的,主要是作用于Servlet生命周期的注解,实现的是在Bean初始化之前自定义操作
@PostConstruct 方法在 Bean初始化中的执行顺序
Constructor(构造方法)@Autowired(依赖注入)@PostConstruct(注释的初始化方法)
Step-3:编写接口测试缓存
@RequestMapping("test")
public String test(Long id) {
String name = LocalCache.cache.get(String.valueOf(id));
if (name != null) {
System.out.println("缓存中存在,查询缓存");
System.out.println(name);
return name;
}
System.out.println("缓存中不存在,查询数据库");
// 查询数据库操作后,queryDataName方法没有写了;
// 大家可以自己配一下Mybatis和JDBC进行数据库查询,达到效果是从库中查出来 name;
name = queryDataName(id);
System.out.println(name);
LocalCache.cache.put(String.valueOf(id),name);
return name;
}
public String queryDataName(Long id) {
String name = UUID.randomUUID().toString();
return name;
}Step-4:结果展示
这个是控制台输出,每个人的随机 UUID 不一致,我这个只是一个样例
id为1的数据添加到了缓存
id为2的数据添加到了缓存
缓存中存在,查询缓存
e2eadabe-3c42-4732-b465-e085ea5faf96
缓存中不存在,查询数据库
942ffe92-454f-4046-87e5-53e8b951d2a1
二、guava local cache 实现
Tips:Guava是Java工具包,Guava Cache是一套非常完善的本地缓存机制(JVM缓存),工具类就是封装平常常用的方法,不需要你重复造轮子,节省开发人员时间,我们一般需要知道怎么使用。其设计来源于CurrentHashMap,可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。
Guava提供以下方面的能力
- 集合 [
collections]- 缓存 [
caching]- 原生类型支持 [
primitives support]- 并发库 [
concurrency libraries]- 通用注解 [
common annotations]- 字符串处理 [
string processing]I/O等等。
Step-1:导入guava 依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>Step-2:使用guava创建简单缓存管理类
为了方便展示,这里面使用了5 秒的缓存保留时间。
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class GuavaLocalCache{
private Cache<String,String> fiveSecondCache = CacheBuilder.newBuilder()
//设置缓存初始大小,应该合理设置,后续会扩容
.initalCapacity(10)
//最大值
.maximumSize(100)
//并发数设置
.concurrencyLevel(5)
//缓存过期时间,写入后5秒钟过期
.expireAfterWrite(5,TimeUnit.SECONDS)
//统计缓存命中率
.recordStats()
.build()
public Cache<String,String> getFiveSecondCache() {
return fiveSecondCache;
} // 这里就是拿到缓存对象。
public void setFiveSecondCache(Cache<String,String> fiveSecondCache) {
this.fiveSecondCache = fiveSecondCache;
}
}Step-3:使用 guava cache,并尝试统计命中率
public class test {
@Autowired
private GuavaLocalCache guavaLocalCache;
@RequestMapping("guavaTest")
public String guavaTest(Long id) {
// 获取缓存
Cache<String,String> fiveSecondCache = guavaLocalCache.getFiveSecondCache();
// 从缓存中获取对象
String nameCache = fiveSecondCache.getIfPresent(String.valueOf(id));
// 缓存中存在
if (nameCache != null) {
System.out.println("缓存命中:"+ nameCache + "," + getCacheStats(fiveSecondCache));
return nameCache;
}
// 将数据存入缓存
System.out.println("缓存未命中," + getCacheStats(fiveSecondCache));
nameCache = id + "-" + UUID.randomUUID().toString();
fiveSecondCache.put(String.valueOf(id),nameCache);
return nameCache;
}
public String getCacheStats(Cache<String,String> cahce) {
CacheStats stats = cache.stats();
return "缓冲命中率:"+stats.hitRate() +" 被清除缓冲数:" + stats.evictionCount();
}
}三、使用redis实现缓存
Tips:Redis(全称:Remote Dictionary Server远程字典服务)是一个开源的使用ANSI C语言 编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库 。Redis一般被用来做缓存用的,它实际上也是一种数据库(非关系型数据库),可以对经常使用到的数据进行存储,也就是大家所说的缓存。官方给出的数据是,Redis能达到10w+的QPS( 每秒查询速度 ) 。
Tips: 为什么Redis的速度比Mysql等这种数据快呢? 因为
Redis存储的是key-values格式的数据,时间复杂度是O(1),即直接通过key查询对应的value。 而如Mysql数据库,底层的实现是B+树,时间复杂度是O(logn)。 最重要的一点是,数据库的数据是存储在磁盘中的,而
Redis是存储在内存当中的,它们的速度差距不言而喻。但Redis也支持持久化存储
Step-1:导入Redis 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>在 SpringBoot 配置文件中加入设置,我使用的是yml 形式的文件,如果没有密码的话不填就好了
redis:
# IP地址
host: XXX.XXX.XXX.XXX
# 密码
password: XXXXXXXX
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 1
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1msStep-2:编写测试接口
public class TestRedis{
// 下面StringRedisTemplate 是一个继承自 RedisTemplate的类
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/redisTest")
public String redisCacheTest(Long id){
String name = stringRedisTemplate.opsForValue().get(String.valueOf(id));
if (name != null){
System.out.println("缓存中存在,查询缓存");
System.out.println(name);
return name;
}
System.out.println("缓存中不存在,查询数据库");
name = id + "-" + UUID.randomUUID().toString();
System.out.println(name);
stringRedisTemplate.opsForValue().set(String.valueOf(id),name);
return name;
}
}Step-3:进行接口测试,并使用Redis DeskTop Manager 进行查看
参考文章
- 【java缓存、redis缓存、guava缓存】java中实现缓存的几种方式_java缓存cache-CSDN博客
- 一篇文章搞定 Redis 基础知识 - 知乎 (zhihu.com)
- Java本地缓存技术选型(Guava Cache、Caffeine、Encache) - 掘金 (juejin.cn)
- MemCache原理超详细解读(仅学习) - 知乎 (zhihu.com)
- PostConstruct注解详细使用说明及理解-CSDN博客
- PostConstruct (Java Platform SE 8 ) (oracle.com)
- Java开发利器Guava Cache之使用篇 - 掘金 (juejin.cn)
- Google guava 工具类的介绍和使用 - 掘金 (juejin.cn)
- Redis详细介绍(精简版)_redis 服务 精简-CSDN博客
- 初识Redis,看这一篇就够了
到此这篇关于Java 实现缓存的三种方式问题汇总的文章就介绍到这了,更多相关Java 实现缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
