我的编程空间,编程开发者的网络收藏夹
学习永远不晚

java基础--自己动手实现一个LRU

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

java基础--自己动手实现一个LRU

LRU,即 Least Recently Use ,直译为 “最近最少使用”。它是根据数据的历史访问记录来进行数据淘汰的,淘汰掉最先访问的数据,其核心思想是 如果数据最近被访问过,那么将来被访问的几率也会更加高。

要实现 LRU,需要做到两点:

  • 查询出最近最晚使用的项
  • 给最近使用的项做一个标记

实现的方案有多种,这里小编主要介绍两种:

  1. LinkedHashMap
  2. 双向链表 + HashMap

LinkedHashMap 实现

利用 LinkedHashMap 的原因就在于 LinkedHashMap 是有序的,默认情况下是按照元素的添加顺序存储的,也可以调整为根据访问顺序来调整内部顺序(设置参数 accessOrder 进行调整),即最近读取的数据放在最前面,我们就是利用 LinkedHashMap 的这个特性来实现 LRU。先来一个简单的例子吧:


    public static void main(String[] args){
        Map<String,String> map = new LinkedHashMap(10,0.75f,true);
 
        map.put("1","a");
        map.put("2","b");
        map.put("3","c");
        map.put("4","d");
 
        System.out.println("原始顺序为:");
        for(Iterator<Map.Entry<String,String>> it = map.entrySet().iterator();it.hasNext();){
            System.out.print(it.next().getKey() + "    ");
        }
        System.out.println();
 
        map.get("2");
 
        System.out.println("访问 4 之后的顺序为:");
        for(Iterator<Map.Entry<String,String>> it = map.entrySet().iterator();it.hasNext();){
            System.out.print(it.next().getKey() + "    ");
        }
    }

运行结果:


原始顺序为:
1    2    3    4    
访问 4 之后的顺序为:
1    3    4    2 

更多关于 LinkedHashMap,请看这篇文章:LinkedHashMap

LinkedHashMap 实现 LRU 有两种方式,一种是继承 LinkedHashMap,一种是利用组合的方式,下面分别演示这两种情况。

继承 LinkedHashMap

采用继承的方式实现起来是非常简单的,因为 LinkedHashMap 本身就已经具备了 LRU 的特性,我们只需要实现一点:当容器中元素个数超过我们设定的容量后,删除第一个元素即可。同时由于 LinkedHashMap 本身不具备线程安全,我们需要确保他线程安全,这个也很简单,重写 LinkedHashMap 的 get() put() 方法即可,或者使用 Collections.synchronizedMap() 方法也可以。实现如下:


public class LRUCacheLinkedHashMap<K,V> extends LinkedHashMap<K,V> {
 
    
    private int capacity;
 
    LRUCacheLinkedHashMap(int capacity){
        // AccessOrder = true
        super(capacity,0.75f,true);
 
        this.capacity = capacity;
    }
 
    
    @Override
    public boolean removeEldestEntry(Map.Entry<K, V> eldest){
        System.out.println(eldest.getKey() + "=" + eldest.getValue());
        return size()>capacity;
    }
 
    @Override
    public synchronized V get(Object key) {
        return super.get(key);
    }
 
    @Override
    public synchronized V put(K key, V value) {
        return super.put(key, value);
    }
}

验证


  public static void main(String[] args){
        LRUCacheLinkedHashMap cache = new LRUCacheLinkedHashMap(5);
 
        cache.put("1","a");
        cache.put("2","b");
        cache.put("3","c");
        cache.put("4","d");
        cache.put("5","e");
 
        System.out.println("插入 5 个元素后的顺序");
        printlnCache(cache);
 
        // 插入第 6 个元素
        cache.put("6","e");
 
        System.out.println("插入第 6 个元素后的顺序");
        printlnCache(cache);
 
        // 访问 第 3 个元素
        cache.get("3");
 
        System.out.println("访问元素 3 后的顺序");
        printlnCache(cache);
 
    }
 
    private static void printlnCache(LRUCacheLinkedHashMap cacheMap){
        for(Iterator<Map.Entry<String,String>> it = cacheMap.entrySet().iterator(); it.hasNext();){
            System.out.print(it.next().getKey() + "    ");
        }
        System.out.println();
    }

运行结果:


插入 5 个元素后的顺序
1    2    3    4    5    
插入第 6 个元素后的顺序
2    3    4    5    6    
访问元素 3 后的顺序
2    4    5    6    3 

运行结果完全符合我们的预期

组合 LinkedHashMap

使用组合的方式可能会更加优雅些,但是由于没有实现 Map 接口,所以就不能使用 Collections.synchronizedMap() 方式来保证线程安全性了,所以需要在每个方法处增加 synchronized 来确保线程安全。实现方式如下:


public class LRUCache<K,V> {
    private int capacity;
 
    private Map<K,V> cacheMap;
 
    public LRUCache(int capacity){
        this.capacity = capacity;
 
        cacheMap = new LinkedHashMap<>(capacity,0.75f,true);
    }
 
    public synchronized void put(K k,V v){
        cacheMap.put(k,v);
 
        // 移除第一个元素
        if(cacheMap.size() > capacity){
            K first = this.keyIterator().next();
 
            cacheMap.remove(first);
        }
    }
 
    public synchronized V get(K k){
        return cacheMap.get(k);
    }
 
    public Iterator<K> keyIterator(){
        return cacheMap.keySet().iterator();
    }
}

验证:


 public static void main(String[] args) {
        LRUCache lruCache = new LRUCache(5);
 
        lruCache.put("1","a");
        lruCache.put("2","b");
        lruCache.put("3","c");
        lruCache.put("4","d");
        lruCache.put("5","e");
 
        System.out.println("插入 5 个元素后的顺序");
        println(lruCache);
 
        // 插入第 6 个元素
        lruCache.put("6","e");
 
        System.out.println("插入 第 6 个元素后的顺序");
        println(lruCache);
 
        // 访问 第 3 个元素
        lruCache.get("3");
 
        System.out.println("访问元素 3 后的顺序");
        println(lruCache);
 
    }
 
    private static void println(LRUCache lruCache){
        for(Iterator it = lruCache.keyIterator(); it.hasNext();){
            System.out.print(it.next() + "    ");
        }
        System.out.println();
    }

运行结果如下:


插入 5 个元素后的顺序
1    2    3    4    5    
插入 第 6 个元素后的顺序
2    3    4    5    6    
访问元素 3 后的顺序
2    4    5    6    3 

组合的方式也显得非常简单,有两点需要注意:

  1. 保证每个方法的线程安全
  2. put 时,需要查看当前容量是否超过设置的容量,超过则需要删除第一个元素。当然小编这种是实现方式不是很优雅,这么做知识为了能够更加好阐述 LRU 的实现。更好的方案是在构造 LinkedHashMap 时,重写removeEldestEntry(),如下:

       cacheMap = new LinkedHashMap<K,V>(capacity,0.75f,true){
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size()>capacity;
            }
        };

链表 + HashMap 实现

我们想想,在不利用现存数据结构的条件(如 LinkedHashMap)如何实现一个 LRU 呢?缓存部分容易实现,我们都知道利用 HashMap 即可,但是如何实现缓存容量不足时丢弃最不常用的数据的功能?

  • 利用时间戳。每一个访问,增加的元素我们都给其更新一个时间戳,在 put 的时候,检查,删除时间戳最小的就可以了。这种方法可以实现,但是代价较高,就是我们需要遍历整个数据,得到最小的时间戳。
  • 我们可以换位思考,我们其实不需要关注每个节点的增加或者遍历时间,我们只需要知道那个节点是最先访问就可以了,所以我们可以利用链表记录访问记录,有新数据加入时放在链表的 head 节点,每次访问也将该数据放在 head 节点,那么链表的 tail 一定是最早访问的节点,所以每次当容量不足的时候删除 tail 节点数据并将它的前驱节点设置为 tail 就可以了。注意,这个链表是一个双向链表。代码如下:

public class LinkedLRUCache<K,V> {
 
    private int capacity;
 
    private Map<K,LRUNode> map;
 
    private LRUNode head;
 
    private LRUNode tail;
 
    LinkedLRUCache(int capacity){
        this.capacity = capacity;
        this.map = new HashMap<>();
    }
 
    public synchronized void put(K k,V v){
        LRUNode node = map.get(k);
 
        // 存在该 key,将节点的设置为 head
        if(node != null){
            node.value = v;
 
            remove(node,false);
        }else{
            
            node = new LRUNode(k,v);
 
            if(map.size() >= capacity){
                //删除 tail 节点
                remove(tail,true);
            }
 
            map.put(k,node);
 
            setHead(node);
        }
 
        // 设置当前节点为首节点
        setHead(node);
    }
 
    public Object get(String key) {
        LRUNode node = map.get(key);
        if (node != null) {
            // 将刚操作的元素放到head
            remove(node, false);
            setHead(node);
            return node.value;
        }
        return null;
    }
 
    
    private void setHead(LRUNode node) {
        if(head != null){
            node.next = head;
            head.prev = node;
        }
 
        head = node;
 
        if(tail == null){
            tail = node;
        }
    }
 
    
    private void remove(LRUNode node,boolean flag) {
        if (node.prev != null) {
            node.prev.next = node.next;
        } else {
            head = node.next;
        }
        if (node.next != null) {
            node.next.prev = node.prev;
        } else {
            tail = node.prev;
        }
        node.next = null;
        node.prev = null;
        if (flag) {
            map.remove(node.key);
        }
    }
    
    private Iterator iterator(){
        return map.keySet().iterator();
    }
 
    private class LRUNode<K,V> {
 
        
        private K key;
 
        
        private V value;
 
        private LRUNode next;
 
        private LRUNode prev;
 
        LRUNode(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }
}

验证


   public static void main(String[] args){
        LRUCache lruCache = new LRUCache(5);
        
        lruCache.put("1","a");
        lruCache.put("2","b");
        lruCache.put("3","c");
        lruCache.put("4","d");
        lruCache.put("5","e");
       
        System.out.println("插入 5 个元素");
        println(lruCache);
 
        System.out.println("插入 3 元素");
        lruCache.put("3","c");
        println(lruCache);
 
        System.out.println("插入第  6 个元素");
        lruCache.put("6","f");
        println(lruCache);
 
        System.out.println("访问 4 元素");
        lruCache.get("4");
        println(lruCache);
    }
    
    private static void println(LRUCache lruCache){
        Iterator iterator = lruCache.keyIterator();
        while (iterator.hasNext()){
            System.out.print(iterator.next() + "    ");
        }
        
        System.out.println();
    }

执行结果:


插入 5 个元素
1    2    3    4    5    
插入 3 元素
1    2    4    5    3    
插入第  6 个元素
2    4    5    3    6    
访问 4 元素
2    5    3    6    4 

到此这篇关于java基础--自己动手实现一个LRU的文章就介绍到这了,更多相关java LRU内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

java基础--自己动手实现一个LRU

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Python怎样手动编写一个自己的LRU缓存装饰器

Python怎样手动编写一个自己的LRU缓存装饰器,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。LRU缓存算法,指的是近期最少使用算法,大体逻辑就是淘汰最长时间没有用的那个缓存
2023-06-22

详解自己动手添加一个函数实现任意字段调用

是否遇到过想调用某些自定义字段,却发现dedecms的标签底层模板字段不包括这个字段呢?这就大大限制了灵活性,但dede也不可能让所有字段都允许调用的,那样就会大大降低系统效率,所以今天分享的是编程客栈一个比较完美解决这个问题的方法,配合d
2022-06-12

基于JS实现Android,iOS一个手势动画效果

废话不多说了,先给大家展示下效果图:这是iOS下的效果,android下完全一致。通过do_GestureView组件和do_Animation组件,deviceone能很容易实现复杂的跨平台纯原生动画效果,这个示例就是通过手势控制图片上下
2022-06-06

Java项目中和实现自动生成一个构造方法

这篇文章给大家介绍Java项目中和实现自动生成一个构造方法,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Java中自动生成构造方法详解每个类在没有声明构造方法的前提下,会自动生成一个不带参数的构造方法,如果类一但声明有
2023-05-31

java实现后台自动登录一个网站,然后对网站进行自动操作

要在Java中实现后台自动登录一个网站并进行自动操作,你可以使用以下步骤:1. 导入所需的库和依赖:在Java代码中,你需要导入一些库和依赖项来处理网络请求和模拟用户操作。常用的库包括Apache HttpClient或OkHttp用于发送
2023-09-08

java实现后台自动登录一个网站,然后对网站进行自动操作

Java实现后台自动登录和网站操作使用Java和SeleniumWebDriver,您可以自动化网站操作,如登录、导航页面和提取数据。示例代码演示了如何登录网站并从仪表板获取数据。通过使用HTTP组件,您可以发送HTTP请求,如获取API数据。释放资源以避免内存泄漏。该自动化简化了工作流程,但需要根据特定网站进行调整。
java实现后台自动登录一个网站,然后对网站进行自动操作
2024-04-13

如何在微信小程序中实现一个手动埋点和自动埋点功能

如何在微信小程序中实现一个手动埋点和自动埋点功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、手动埋点手动埋点就是在每一处需要的地方,都加一段上报埋点的代码。影响代码的
2023-06-06

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录