浅谈Java如何实现一个基于LRU时间复杂度为O(1)的缓存
作者:码农小麦 发布时间:2022-02-02 08:35:36
标签:Java,LRU,缓存
LRU:Least Recently Used最近最少使用,当缓存容量不足时,先淘汰最近最少使用的数据。就像JVM垃圾回收一样,希望将存活的对象移动到内存的一端,然后清除其余空间。
缓存基本操作就是读、写、淘汰删除。
读操作时间复杂度为O(1)的那就是hash操作了,可以使用HashMap索引 key。
写操作时间复杂度为O(1),使用链表结构,在链表的一端插入节点,是可以完成O(1)操作,但是为了配合读,还要再次将节点放入HashMap中,put操作最优是O(1),最差是O(n)。
不少童鞋就有疑问了,写入时又使用map进行了put操作,为何缓存不直接使用map?没错,首先使用map存储了节点数据就是采用空间换时间,但是淘汰删除不好处理,使用map如何去记录最近最少使用(涉及到时间、频次问题)。so,使用链表可以将活跃节点移动到链表的一端,淘汰时直接从另一端进行删除。
public class LruCache<K,V> {
/** 这里简单点直接初始化了*/
private int capacity = 2;
private int size = 0;
private HashMap<K,DoubleListNode<K,V>> cache = new HashMap<>(capacity);
private DoubleListNode<K,V> lruNode = new DoubleListNode<K, V>(null,null,null,null);
private DoubleListNode<K,V> mruNode = new DoubleListNode<K, V>(null,null,null,null);
public V get(K key){
DoubleListNode<K,V> target = cache.get(key);
if (target == null) {
return null;
}
/** 使用过就移动到右侧 */
move2mru(target);
return target.value;
}
public void put(K key,V value){
if(cache.containsKey(key)){
DoubleListNode<K,V> temp = cache.get(key);
temp.value = value;
/** 使用过就移动到右侧 */
move2mru(temp);
return;
}
/** 容量满了清除左侧 */
if(size >= capacity){
evict4lru();
}
DoubleListNode<K,V> newNode = new DoubleListNode<>(mruNode,null,key,value);
if(size == 0){
lruNode.next = newNode;
}
mruNode.next = newNode;
mruNode = newNode;
cache.put(key,newNode);
size++;
}
private void move2mru(DoubleListNode<K,V> newMru){
DoubleListNode<K,V> pre = newMru.pre;
DoubleListNode<K,V> next = newMru.next;
pre.next = next;
newMru.pre = mruNode;
mruNode.next = newMru;
mruNode = newMru;
}
private void evict4lru(){
cache.remove(lruNode.next.key);
lruNode.next = lruNode.next.next;
size--;
}
public String toString(){
StringBuffer sb = new StringBuffer("lru -> ");
DoubleListNode<K,V> temp = lruNode;
while(temp!=null){
sb.append(temp.key).append(":").append(temp.value);
sb.append(" -> ");
temp = temp.next;
}
sb.append(" -> mru ");
return sb.toString();
}
public static void main(String[] args) {
LruCache<String,String> cache = new LruCache<>();
cache.put("1","1");
System.out.println(cache);
cache.get("1");
cache.put("2","2");
System.out.println(cache);
cache.put("3","3");
System.out.println(cache);
cache.put("4","4");
System.out.println(cache);
}
}
class DoubleListNode<K,V>{
K key;
V value;
DoubleListNode<K,V> pre;
DoubleListNode<K,V> next;
public DoubleListNode(K key,V value){
this.key = key;
this.value = value;
}
public DoubleListNode(DoubleListNode<K,V> pre,DoubleListNode<K,V> next,K key,V value){
this.pre = pre;
this.next = next;
this.key = key;
this.value = value;
}
}
这里使用链表,及HashMap完成了基于LRU的缓存,其中HashMap主要用来快速索引key,链表用来完成LRU机制。当然尚有许多不足,包括缓存移除remove,缓存ttl,线程安全等。
来源:https://blog.csdn.net/weixin_43275277/article/details/107740004


猜你喜欢
- 这篇文章主要介绍了Java日期与时间类原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参
- package com.eboy.testyaoyiyao; import java.text.SimpleDateFormat; impo
- 操作字符串的类都有哪些?区别是什么?操作字符串的类主要用三个,分别是String类,StringBuffer类和StringBuilder类
- 前言一直对它们之间的关系感到好奇,SpringBoot既然是Spring的封装,那么SpringBoot在初始化时应该也会有Bean的加载,
- 最新需要公司要求在不改变原来的登录逻辑的情况下,将原来的验证码登录的形式改成滑动图片的形式!下面是做出来的效果:实现思路:所有的图片数据,验
- Java中List.of()和Arrays.asList()的区别及原因动手写一下,让自己更有印象1.Arrays.asList()可以插入
- WPF换肤的设计原理,利用资源字典为每种皮肤资源添加不同的样式,在后台切换皮肤资源文件。截图上图中,第一张图采用规则样式,第二张图采用不规则
- 这几天面试中有遇到关于main数组中的args数组传值的问题,一般是从命令提示符中传值,也可以直接在java代码中赋值。而且这个数组的长度是
- 本文介绍了Flutter 实现下拉刷新上拉加载的示例代码,分享给大家,具体如下:效果图 使用方法添加依赖depende
- 一、前言我们经常会遇到业务想看debug日志的问题,但是debug日志频繁打印会对日志查看有影响,且日志多对系统也会有一定的压力,因此,如果
- 现在面试,基本上都是面试造火箭🚀,工作拧螺丝🔩。而且是喜欢问一些 Spring 相关的知识点,比如 @Autowired 和 @Resour
- 定义:当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。特
- java中对List分段操作的实例问题:假设A系统查询出来一个很大很大的List,现在B系统想要得到这个List来导出报表,但是B系统部署环
- 通过输入流来读取客户端信息,相应的时候通过输出流来实现。服务端类的代码:import java.io.BufferedReader;impo
- 一、背景假如:黑客黑进了数据库,或者离职人员导出了数据,那么就可能导致这些敏感数据的泄漏。因此我们就需要找到一种方法来解决这个问题。二、解决
- 用注解实现Mybatis插入数据返回自增的主键Id我们在数据库表设计的时候,一般都会在表中设计一个自增的id作为表的主键。这个id也会关联到
- 系列目录 【已更新最新开发文章,点击查看详细】类似于以下场景,将表单中的用户信息(包含附件)上传到服务器并保存到数据库中,<form
- 设置项这个版本已经取消了defalut settings指定成默认配置的选项,所以配置都是在settings中配置设置项设置统一UTF-8编
- 本文实例讲述了Java使用Iterator迭代器遍历集合数据的方法。分享给大家供大家参考,具体如下:1、使用迭代器遍历ArrayList集合
- 在项目中遇到try...catch...语句,因为对Java异常处理机制的流程不是很清楚,导致对相关逻辑代码不理解。所以现在来总结Java异