一、Redis是单线程的(Maven仓库更新慢)
安装
Redis的安装不难,w10下就是下载下来,然后解压,在命令窗口cd到安装路径下,启动: redis-server.exe redis.windows.conf;在环境变量的path下把Redis的安装目录加进去。之后在cmd的命令窗口下启动:redis-server。另开一个命令窗口,链接Redis: redis-cli.exe -h 127.0.0.1 -p6379
键的常用命令
设置一个key值为value set key value
获得key值得value get key
查看所有键 keys *
判断某key否存在 exists key
从当前库移除 move key db
给key设置过期时间(秒) expire key
查看还有多少秒过期(-1永不过期,-2已过期) ttl key
查看key是什么类型 type key
二、五大数据类型
-
String型:String 是redis中最基本的数据类型,二进制安全的,即它可以包含任何数据,如序列化的对象、jpg图片,大小上限是512M。
-
Hash型(存储消耗高于字符串):键值对集合,适合存储对象,类似 Java的Map<String,Object>。
-
List型: 字符串链表,按插入顺序排序,可重复。
-
Set: Set 无顺序,不能重复。
-
SortedSet(zset): 不重复,有顺序。适合做排行榜,排序需要一个分数属性。
三、Redis持久化
RDB持久化
将Reids在内存中的数据库记录定时 dump到磁盘上的RDB持久化.在指定的时间间隔内将内存中的数据集快照写入磁盘,是fork一个子进程,先将数据集写入临时文件,写入成功后,替换之前的文件,用二进制压缩存储。
优势
1.整个Redis数据库将只包含一个文件,便于文件备份,易恢复记录。
2.性能最大化。开始持久化时只fork出子进程,由子进程完成这些持久化的工作,避免服务进程执行IO操作。
3.数据集很大RDB的启动效率高。
劣势
1.持久化之前宕机,没写入磁盘的数据会丢失,无法保证数据的高可用性,即最大限度的避免数据丢失。
2.通过fork子进程来协助完成数据持久化工作的,数据集较大,可能会导致整个服务器停止服务几百毫秒,甚至更长时间。
RDB持久化常用配置
通过配置文件修改快照的频率,打开6379.conf文件搜save。
# 时间策略
save 900 1 #在900秒,1个以上key发生变化,则dump内存快照。
save 300 10 #在300秒,10个以上key发生变化,则dump内存快照。
save 60 10000 #在60秒,10000个以上key发生变化,则dump内存快照。
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
# 导入时是否检查
rdbchecksum yes
AOF持久化
是将Reids的操作日志以追加的方式写入文件。以日志的形式记录服务器所处理的每一个写、删除操作.查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
优势
1.更高的数据安全性。Redis中提供每秒同步、每修改同步、不同步三种策略。每修改同步,可视为同步持久化。
2.对日志文件的写入操作采用的是append模式,出现宕机,不会破坏日志文件中已经存在的内容。
3.如果日志过大,自启rewrite机制。即Redis以append模式将修改数据写入文件中,同时Redis会建一个新文件记录此间修改命令。进行rewrite切换时可更好的保证数据安全性。
4. AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。
劣势
1.AOF占空间大,AOF的恢复速度慢。
2.AOF在运行效率会慢于RDB。
AOF持久化常用配置
# 是否开启aof
appendonly yes
# 文件名称
appendfilename "appendonly.aof"
# 同步方式
appendfsync always #每次有数据修改发生时都会写入AOF文件。
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
appendfsync no #从不同步,高效但是数据不会被持久化。
# aof重写期间是否同步
no-appendfsync-on-rewrite no
# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 加载aof时如果有错如何处理
aof-load-truncated yes
# 文件重写策略
aof-rewrite-incremental-fsync yes
四、Redis事务
事务顺序
1.事务开始
2.命令入队
3.事务执行
事务命令(将多个命令打包, 然后一次性、按顺序地执行)
MULTI、DISCARD、EXEC、WATCH
测试
> MULTI #开始事务
OK
> SET name "Jun_South" #命令入队
QUEUED
> GET name #命令入队
QUEUED
> SADD tag "Jun" "South" "Liu" #命令入队
QUEUED
> SMEMBERS tag #命令入队
QUEUED
> EXEC #执行
1) OK
2) "Jun_South"
3) (integer) 3
4) 1) "Jun"
2) "South"
3) "Liu"
说明:
DISCARD取消事务,清空整个事务队列,从事务状态调整回非事务状态,返回OK说明事务已被取消。Redis的事务不可嵌套,当客户端已经处于事务状态,再发送 MULTI 时,服务器返回一个错误,不会造成事务失败,
继续等待其他命令的入队(WATCH与MULTI一样)。
WATCH 在事务开始之前监视任意数量的键:当EXEC命令执行事务时,被监视的键被其他客户端改了,那么整个事务不再执行,返回失败。
例如
> WATCH name
OK
> MULTI
OK
> SET name peter
QUEUED
> EXEC
(nil)
五、主从复制
开启主从复制
主从复制的开启主节点啥也不做。
从节点开启主从复制,有3种方式
1.配置文件:在从服务器的配置文件中加入: slaveof <masterip> <masterport>(slaveof 主端口号)
2.启动命令:redis-server启动命令后加入 --slaveof <masterip> <masterport>(slaveof 主端口号)
3.客户端命令:Redis服务器启动后,直接通过客户端执行命令:slaveof <masterip> <masterport>(slaveof 主端口号),则该Redis实例成为从节点。
通过info replication 命令可以看到复制的一些信息。
断开主从复制
命令:slaveof no one
主从复制过程
连接建立(即准备)、数据同步、命令传播。
六、Java中的Jedis
引入依赖或jar包
<!-- 引入Redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
JedisPool池使用
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisPool {
private static JedisPool pool;//jedis连接池
private static int maxTotal = 30;//最大连接数
private static int maxIdle = 10;//最大空闲连接数
private static int minIdle = 5;//最小空闲连接数
private static boolean testOnBorrow = true;//在取连接时测试连接的可用性
private static boolean testOnReturn = false;//再还连接时不测试连接的可用性
static {
initPool();//初始化连接池
}
public static Jedis getJedis(){
return pool.getResource();
}
public static void close(Jedis jedis){
jedis.close();
}
private static void initPool(){
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReturn(testOnReturn);
config.setBlockWhenExhausted(true);
pool = new JedisPool(config, "127.0.0.1", 6379, 5000);
}
}
RedisPoolUtil
import redis.clients.jedis.Jedis;
public class RedisPoolUtil {
private RedisPoolUtil(){}
private static RedisPool redisPool;
public static String get(String key){
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.get(key);
} catch (Exception e){
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
return result;
}
}
public static Long setnx(String key, String value){
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.setnx(key, value);
} catch (Exception e){
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
return result;
}
}
public static String getSet(String key, String value){
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.getSet(key, value);
} catch (Exception e){
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
return result;
}
}
public static Long expire(String key, int seconds){
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.expire(key, seconds);
} catch (Exception e){
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
return result;
}
}
public static Long del(String key){
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.del(key);
} catch (Exception e){
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
return result;
}
}
}
Redis的延时队列Demo
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import redis.clients.jedis.Jedis;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.UUID;
//延时队列
public class RedisDelayingQueue<T> {
static class TaskItem<T>{
public String id;
public T msg;
}
private Type TaskType = new TypeReference<TaskItem<T>>(){}.getType();
private Jedis jedis;
private String queueKey;
public RedisDelayingQueue(Jedis jedis, String queueKey) {
this.jedis = jedis;
this.queueKey = queueKey;
}
public void delay(T msg){
TaskItem<T> task = new TaskItem<>();
//分配id
task.id = UUID.randomUUID().toString();
task.msg = msg;
//fastjson序列化
String s = JSON.toJSONString(task);
//塞入延时队列,5秒后再试。
jedis.zadd(queueKey,System.currentTimeMillis()+5000,s);
}
public void loop(){
while (!Thread.interrupted()){
//只取一条
Set<String> values = jedis.zrangeByScore(queueKey,0,System.currentTimeMillis(),0,1);
if(values.isEmpty()){
try {
//延时0.5秒
Thread.sleep(500);
}catch (InterruptedException e){
break;
}
continue;
}
String s = values.iterator().next();
//抢到了
if(jedis.zrem(queueKey,s)>0){
// fastjson 反序列化
TaskItem<T> task = JSON.parseObject(s,TaskType);
this.handleMsg(task.msg);
}
}
}
public void handleMsg(T msg){
System.out.println("msg: "+msg);
}
public static void main(String[] args) {
Jedis jedis = RedisPool.getJedis();
RedisDelayingQueue<String> queue = new RedisDelayingQueue<>(jedis,"South-Demo");
Thread producer = new Thread(){
public void run(){
for(int i=0;i<15;i++){
queue.delay("codehole"+i);
}
}
};
Thread consumer = new Thread(){
public void run(){
queue.loop();
}
};
producer.start();
consumer.start();
try{
producer.join();
Thread.sleep(3000);
consumer.interrupt();
consumer.join();
}catch (InterruptedException e){
}
}
}
简单的Redis限流Demo
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
import java.io.IOException;
//简单的Redis限流
public class SimpleRateLimiter {
private Jedis jedis;
public SimpleRateLimiter(Jedis jedis) {
this.jedis = jedis;
}
public boolean isActionAllowed(String userId,String actionKey,int period,int maxCount)throws IOException{
String key = String.format("hist:%s:%s",userId,actionKey);
long nowTs = System.currentTimeMillis();
Pipeline pipe = jedis.pipelined();
pipe.multi();
pipe.zadd(key,nowTs,""+nowTs);
pipe.zremrangeByScore(key,0,nowTs-period*1000);
Response<Long> count = pipe.zcard(key);
pipe.expire(key,period-1);
pipe.exec();
pipe.close();
return count.get()<=maxCount;
}
public static void main(String[] args) throws Exception{
Jedis jedis = RedisPool.getJedis();
SimpleRateLimiter limiter = new SimpleRateLimiter(jedis);
for(int i=0;i<20;i++){
System.out.println(limiter.isActionAllowed("JunSouth","reply",60,5));
}
}
}
Redis分布式锁Demo
import redis.clients.jedis.Jedis;
import java.util.Collections;
public class Test01 {
public static void main(String[] args) {
/*
setnx(key, value):“set if not exits”,若该key-value不存在,则成功加入缓存并且返回1,否则返回0。
get(key):获得key对应的value值,若不存在则返回nil。
getset(key, value):先获取key对应的value值,若不存在则返回nil,然后将旧的value更新为新的value。
expire(key, seconds):设置key-value的有效期为seconds秒。
*/
Jedis jedis = RedisPool.getJedis();
String lockKey = "myLock";
String requestId = "myRequestId";
boolean flag = tryGetDistributedLock(jedis,lockKey,requestId,300);
System.out.println("flag: "+flag);
String lockKey2= "myLock2";
String requestId2 = "myRequestId2";
boolean flag2 = releaseDistributedLock(jedis,lockKey,requestId);
System.out.println("flag2: "+flag2);
}
// 错误用法
public static boolean lock(String lockName){//lockName可以为共享变量名,也可以为方法名,主要是用于模拟锁信息
System.out.println(Thread.currentThread() + "开始尝试加锁!");
Long result = RedisPoolUtil.setnx(lockName, String.valueOf(System.currentTimeMillis() + 5000)); //加锁
if (result != null && result.intValue() == 1){
System.out.println(Thread.currentThread() + "加锁成功!");
RedisPoolUtil.expire(lockName, 5); //设置执行时间
System.out.println(""+Thread.currentThread() + "执行业务逻辑!");
RedisPoolUtil.del(lockName);
return true;
} else {
String lockValueA = RedisPoolUtil.get(lockName);
if (lockValueA != null && Long.parseLong(lockValueA) >= System.currentTimeMillis()){
String lockValueB = RedisPoolUtil.getSet(lockName, String.valueOf(System.currentTimeMillis() + 5000)); //加锁
if (lockValueB == null || lockValueB.equals(lockValueA)){
System.out.println(Thread.currentThread() + "加锁成功!");
RedisPoolUtil.expire(lockName, 5); //设置执行时间
System.out.println(Thread.currentThread() + "执行业务逻辑!");
RedisPoolUtil.del(lockName);
return true;
} else {
return false;
}
} else {
return false;
}
}
}
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis,String lockKey,String requestId,int expireTime) {
String result = jedis.set(lockKey,requestId,SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME,expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
Redisq其它一些重要的方法
import redis.clients.jedis.Jedis;
public class Test02 {
public static void main(String[] args) {
}
//位图(适用于签到)
public static void setAndGet(){
/*
setbit
设置特定key对应的比特位的值。
getbit
获取特定key对应的比特位的值。
bitcount
统计给定key对应的字符串比特位为1的数量。
*/
Jedis jedis = RedisPool.getJedis();
System.out.println(jedis.setbit("Jun",1,true));
System.out.println(jedis.setbit("Jun",2,true));
System.out.println(jedis.getbit("Jun",1));
System.out.println(jedis.getbit("Jun",2));
System.out.println(jedis.getbit("Jun",3));
//统计
System.out.println(jedis.bitpos("Jun",true));
System.out.println(jedis.bitcount("Jun"));
jedis.bitfield("","","");
}
//HyperLogLog(适于统计UV)
public static void test_HyperLogLog(){
Jedis jedis = RedisPool.getJedis();
for(int i=0;i<1000;i++){
jedis.pfadd("pf_jun","Jun"+i);//内容去重
}
System.out.println(jedis.pfcount("pf_jun"));
}
// 布隆(4.0开始提供,强大去重功能,适用于过虑用户看过数据。)
public static void test_Bloom(){
/**
得下插件,有点麻烦,没有搞的欲望了!
*/
}
}
主从复制
//主
Jedis jedis_M = new Jedis("127.0.0.1",6379);
//从
Jedis jedis_S = new Jedis("127.0.0.1",6380);
//主从关联
jedis_S.slaveof("127.0.0.1",6379);
//读写分离
jedis_M.set("name","JunSouth");
String result = jedis_S.get("name");
七、Redis常见问题
1、缓存雪崩
1.设置缓存时用了相同的过期时间,导致某一时刻同时失效,请求全部转发到DB,DB崩了。
2.缓存雪崩问题排查
在一个较短的时间内,缓存中较多的key集中过期
此周期内请求访问过期的数据,redis未命中,redis向数据库获取数据
数据库同时接收到大量的请求无法及时处理
Redis大量请求被积压,开始出现超时现象
数据库流量激增,数据库崩溃
重启后仍然面对缓存中无数据可用
Redis服务器资源被严重占用,Redis服务器崩溃
Redis集群呈现崩塌,集群瓦解
应用服务器无法及时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃
应用服务器,redis,数据库全部重启,效果不理想
3.解决方案
更多的页面静态化处理
构建多级缓存架构 Nginx缓存+redis缓存+ehcache缓存
检测Mysql严重耗时业务进行优化 对数据库的瓶颈排查: 例如超时查询、耗时较高事务等
灾难预警机制
监控redis服务器性能指标
CPU占用、CPU使用率
内存容量
查询平均响应时间
线程数
限流、降级 短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐步放开访问。
数据有效期策略调整
根据业务数据有效期进行分类错峰,A类90分钟,B类80分钟,C类70分钟
过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量
超热数据使用永久key
定期维护(自动+人工) 对即将过期数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时。
2、缓存预热
1.系统上线初期,将相关的缓存数据直接加载到缓存系统,可避免大量用户直接查询数据库。
2.问题排查
请求数量较高
主从之间数据吞吐量较大,数据同步操作频度较高
3.解决方案
前置准备工作:
日常例行统计数据访问记录,统计访问频度较高的热点数据
利用LRU数据删除策略,构建数据留存队列。如:storm与kafka配合
3、缓存穿透
1.用户查询数据,库中没有,缓存中也没有,导致每次请求直接查库。
2.解决方案
(1).缓存空值:把数据库返回的空值进行缓存,设置较短的过期时间。
(2).采用布隆过滤器BloomFilter: 在缓存之前在加一层BloomFilter,在查询时先查BloomFilter的key是否存在,如果不存在就直接返回,存在再去查询缓存,缓存中没有再去查询数据库.
4、缓存降级
缓存失效或缓存服务挂掉的情况下,也不去访问数据库,直接访问内存部分数据缓存或直接返回默认数据。
这是有损的操作,尽量减少降级对于业务的影响程度。
5、缓存击穿
1、大量的请求同时查一个key时,此时这个key失效,导致大量的请求都打到数据库上面去。
2.问题排查
Redis中某大访问量的key过期.
多个数据请求压倒服务器上,均未命中.
Redis短时间内向数据库发起大大量同一访问.
3.解决方案
(1). 使用互斥锁(mutex key)
只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据。单机可用synchronized或者lock来处理,分布式环境可用分布式锁就(memcache的add,redis的setnx,zookeeper)。
(2).提前使用互斥锁(mutex key)
在value内部设置超时值timeout1,timeout1比实际的redis timeout(timeout2)小。从cache读取到timeout1发现它过期时,延长timeout1并重新设置到cache,再从数据库加载数据并设置到cache中。
(3).永远不过期
热点key不设置过期时间。把过期时间存在key对应的value里,发现过期,通过一个后台的异步线程进行缓存的构建。
(4).缓存屏障
class MyCache{
private ConcurrentHashMap<String, String> map;
private CountDownLatch countDownLatch;
private AtomicInteger atomicInteger;
public MyCache(ConcurrentHashMap<String, String> map, CountDownLatch countDownLatch,
AtomicInteger atomicInteger) {
this.map = map;
this.countDownLatch = countDownLatch;
this.atomicInteger = atomicInteger;
}
public String get(String key){
String value = map.get(key);
if (value != null){
System.out.println(Thread.currentThread().getName()+"\t 线程获取value值 value="+value);
return value;
}
// 如果没获取到值
// 首先尝试获取token,然后去查询db,初始化化缓存;
// 如果没有获取到token,超时等待
if (atomicInteger.compareAndSet(0,1)){
System.out.println(Thread.currentThread().getName()+"\t 线程获取token");
return null;
}
// 其他线程超时等待
try {
System.out.println(Thread.currentThread().getName()+"\t 线程没有获取token,等待中。。。");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 初始化缓存成功,等待线程被唤醒
// 等待线程等待超时,自动唤醒
System.out.println(Thread.currentThread().getName()+"\t 线程被唤醒,获取value ="+map.get("key"));
return map.get(key);
}
public void put(String key, String value){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
// 更新状态
atomicInteger.compareAndSet(1, 2);
// 通知其他线程
countDownLatch.countDown();
System.out.println();
System.out.println(Thread.currentThread().getName()+"\t 线程初始化缓存成功!value ="+map.get("key"));
}
}
class MyThread implements Runnable{
private MyCache myCache;
public MyThread(MyCache myCache) {
this.myCache = myCache;
}
@Override
public void run() {
String value = myCache.get("key");
if (value == null){
myCache.put("key","value");
}
}
}
public class CountDownLatchDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache(new ConcurrentHashMap<>(), new CountDownLatch(1), new AtomicInteger(0));
MyThread myThread = new MyThread(myCache);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executorService.execute(myThread);
}
}
}
八、Redis内存满了
1、修改配置
1.Redis安装目录下的redis.conf配置文件中添加以下配置设置内存大小
//设置Redis最大占用内存大小为100M
maxmemory 100mb
2.通过命令动态修改内存大小
//设置Redis最大占用内存大小为100M
127.0.0.1:6379> config set maxmemory 100mb
//获取设置的Redis能使用的最大内存大小(64位操作系统下不限制内存大小)
127.0.0.1:6379> config get maxmemory
2、内存淘汰
Redis的淘汰策略
noeviction(默认策略): 对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外).
allkeys-lru: 所有key中使用LRU算法进行淘汰.
volatile-lru: 过期时间的key中使用LRU算法进行淘汰.
allkeys-random: 从所有key中随机淘汰数据.
volatile-random: 过期时间的key中随机淘汰.
volatile-ttl: 过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰.
获取当前内存淘汰策略: 127.0.0.1:6379> config get maxmemory-polic
通过配置文件设置淘汰策略(修改redis.conf文件): maxmemory-policy allkeys-lru
通过命令修改淘汰策略: 127.0.0.1:6379> config set maxmemory-policy allkeys-lru
3、LFU算法(Redis4.0)
淘汰不常用的。LFU算法能表示一个key被访问的热度。
LFU一共有两种策略:
volatile-lfu: 设置了过期时间的key中使用LFU算法淘汰key。
allkeys-lfu: 所有的key中使用LFU算法淘汰数据。
4、LRU算法
淘汰最近没用到的
public class LRUCache<k, v> {
//容量
private int capacity;
//当前有多少节点的统计
private int count;
//缓存节点
private Map<k, Node<k, v>> nodeMap;
private Node<k, v> head;
private Node<k, v> tail;
public LRUCache(int capacity) {
if (capacity < 1) {
throw new IllegalArgumentException(String.valueOf(capacity));
}
this.capacity = capacity;
this.nodeMap = new HashMap<>();
//初始化头节点和尾节点,用哨兵模式减少判断头结点和尾节点为空的代码
Node headNode = new Node(null, null);
Node tailNode = new Node(null, null);
headNode.next = tailNode;
tailNode.pre = headNode;
this.head = headNode;
this.tail = tailNode;
}
public void put(k key, v value) {
Node<k, v> node = nodeMap.get(key);
if (node == null) {
if (count >= capacity) {
//先移除一个节点
removeNode();
}
node = new Node<>(key, value);
//添加节点
addNode(node);
} else {
//移动节点到头节点
moveNodeToHead(node);
}
}
public Node<k, v> get(k key) {
Node<k, v> node = nodeMap.get(key);
if (node != null) {
moveNodeToHead(node);
}
return node;
}
private void removeNode() {
Node node = tail.pre;
//从链表里面移除
removeFromList(node);
nodeMap.remove(node.key);
count--;
}
private void removeFromList(Node<k, v> node) {
Node pre = node.pre;
Node next = node.next;
pre.next = next;
next.pre = pre;
node.next = null;
node.pre = null;
}
private void addNode(Node<k, v> node) {
//添加节点到头部
addToHead(node);
nodeMap.put(node.key, node);
count++;
}
private void addToHead(Node<k, v> node) {
Node next = head.next;
next.pre = node;
node.next = next;
node.pre = head;
head.next = node;
}
public void moveNodeToHead(Node<k, v> node) {
//从链表里面移除
removeFromList(node);
//添加节点到头部
addToHead(node);
}
class Node<k, v> {
k key;
v value;
Node pre;
Node next;
public Node(k key, v value) {
this.key = key;
this.value = value;
}
}
}