Java中的LinkedHashMap及LRU缓存机制详解

 更新时间:2023年09月19日 10:04:00   作者:强钦钦  
这篇文章主要介绍了Java中的LinkedHashMap及LRU缓存机制详解,LinkedHashMap继承自HashMap,它的多种操作都是建立在HashMap操作的基础上的,同HashMap不同的是,LinkedHashMap维护了一个Entry的双向链表,保证了插入的Entry中的顺序,需要的朋友可以参考下

LinkedHashMap

  1. 维持插入顺序;如 (1,“a”) (2, “b”)(先插的先访问)
  2. 维持访问顺序(将最近访问的数据移到链表的尾部 LRU思想 afterNodeAccess(里面处理了Entry的before after属性))
  3. 主要是底层维护了一个双向链表
  4. 不能被克隆和序列化(HashMap可以)
  5. LinkedHashMap的put类似于HashMap的 ; LinkedKeyIterator里调用nextNode() 源码716行 modcount fast-fail

实现LRU缓存机制

  • Least Recently Used的缩写,即最近最少使用
  • LRU缓存机制类似LinkedHashMap的双向链表
  • LRU缓存:内存访问时,设计一个缓存(如内存4G,其中1G设置为缓存),大小固定,读取内存数据,首先会去找缓存是否命中
    • 如果命中,直接返回
    • 反之,未命中从内存中读取数据,把数据继续添加到缓存当中,
    • 如果缓存已满,删除访问时间最早的数据

代码测试验证看下面的LRU代码。

1)使用LinkedHashMap实现LRU缓存

需要重写removeEldestEntry方法,该方法:

a. 通过 返回结果 去删除访问 时间最早 的数据

b. map的size()与给定缓存的最大size比较,如果map.size > MaxSize,则return true

c. 参数Map.Entry<K,V> eldest

package collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
class LRUCache<K,V> extends LinkedHashMap<K,V>{
    private int maxSize; //缓存的大小
    //重写
    public LRUCache(int maxSize){
        super(16, 0.75f, true);
        this.maxSize = maxSize;
    }
    //protected
    @Override
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest){
        return size() > maxSize;//根据大小,决定是否删除最早结点
    }
}
public class Teacher_1_15_LinkedHashMap {
    public static void main(String[] args) {
    	//使用LinkedHashMap实现LRU缓存
    	//大小为3
        LRUCache<String, String> cache = new LRUCache<>(3);
        cache.put("a", "jfdshjhf");
        //a
        cache.put("b", "fdjhfjf");
        //a - > b
        cache.put("c", "all");
        //a -> b -> c
        cache.get("a");
        //b -> c -> a
        cache.put("d", "tulun");
        //c -> a -> d
        System.out.println(cache);
        //维护插入顺序
//        LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
//        map.put(1, "ajhd");
//        map.put(5, "fsd");
//        map.put(12, "ref");
//        map.put(10, "gdfs");
//        map.put(19, "hgf");
//        map.put(21, "hgf");
//
//        Iterator<Integer> iterator = map.keySet().iterator();
//        while(iterator.hasNext()){
//            System.out.println(iterator.next());
//        }
//        //维持访问顺序                                                                                                                                                                                             加载因子:f类型      accessOrder
//        LinkedHashMap<Integer, String> map = new LinkedHashMap<Integer, String>(16, 0.75f, true);
//        map.put(1, "ajhd");
//        map.put(5, "fsd");
//        map.put(12, "ref");
//        map.put(10, "gdfs");
//        map.put(19, "hgf");
//        map.put(21, "hgf");
//
//        map.get(12);
//        map.get(10);
//
//        Iterator<Integer> iterator = map.keySet().iterator();
//        while(iterator.hasNext()){
//            System.out.println(iterator.next());
//        }
    }
}

2)简单的自定义实现(笔试)

设置头结点 尾节点

存数据的容器:HashMap

维护双向链表,确保 数据按照访问时间存储

package collection;
import java.util.HashMap;
import javax.swing.text.html.HTMLDocument.Iterator;
class MyLRUCache<K,V>{
    private Node<K,V> head = new Node<>();//头结点    无参构造
    private Node<K,V> tail = new Node<>();//尾
    private final HashMap<Integer,Node> hashMap = new HashMap<>();//new了,作为容器
    private int size;  //实际大小
    private int capacity;//最大容量
    class Node<K, V>{
        private K key;
        private V value;
        private Node pre;//双向链表
        private Node next;
        //构造器
        public Node(){
        }
        public Node(K key, V value){
            this.key = key;
            this.value = value;
        }
    }
    //初始化
    public MyLRUCache(int capacity){
        head.next = tail;//先指向无效的结点,专门指向 头和尾
        tail.pre = head;
        this.capacity = capacity;
        size = 0;
    }
    //添加某个节点(尾插法)
    private void add(Node node){
    	//put里已判断key是否存在,这里只管添加
    	//空表
    	if(tail.pre==head) {
    		head.next=node;//由head指向node
    		//node为首结点,前面再无,故不用设置node.pre
    		tail.pre=node;
    	}else {
    		//Tail之后添加
    		node.pre=tail.pre;
    		tail.pre.next=node;
    		tail.pre=node;
    	}	
    }
    //删除某个节点
    private void remove(Node node){
    	//非空表
    	if(head.next!=tail) {//比地址
    		//头结点
    		if(node==head.next) {
        		head.next=head.next.next; //新头结点
        		//head.next.pre=null; //新头结点不需要设置pre
        	}else if(node==tail.pre) {
        		//尾结点
        		tail.pre=tail.pre.pre;//新尾结点
        		//tail.pre.pre.next=null;
        	}else {
        		//其他
        		node.pre.next=node.next;//要删除结点的前面结点的next指针往后指一个
            	node.next.pre=node.pre;//要删除结点的后面结点的 pre指针往前指一个
        	}   
    	}	
    }
    //添加key-value键值对
    public void put(K key, V value){
        Node node = hashMap.get(key);//hashMap里已存有相同key的结点
        if(node != null){
            node.value = value;
            //从链表中删除node节点
            remove(node);
            //再将node节点尾插法重新插入链表(因为它刚被访问)
            add(node);
        }else{
            if(size < capacity){
                size++;
            }else{
                //超容量(1个),删除缓存中最近最少使用的节点head.next
            	//注意先从HashMap里删除,不然remove会改变head.next值
            	hashMap.remove(head.next.key);//删除指定key的结点
            	remove(head.next);//head.next变为指向head.next.next了
            }
            //将newNode尾插法插入链表
            Node newNode = new Node(key, value);//将输入的key value包装成node
            hashMap.put((Integer) key, newNode);//存到hashMap,键为随机值,值为node结点        (int)(Math.random()*100)
            add(newNode);
        }
    }
    //获取value(返回8,表示成功)(将访问的元素移到链表尾)
    public int get(K key){
        Node node = hashMap.get(key);
        if(node == null){
            return -1;
        }
        //删除node
        hashMap.remove(node.key);//删除指定key的结点
        remove(node);
        //尾插法重新插入
        add(node);
        hashMap.put((Integer) key, node);
        //等价于个数没变
        return 8;
    }
    public void show() {
    	Node<K,V> temp = head;
    	while(temp!=tail.pre){//tail.pre指向的是末尾节点
    		temp=temp.next;
    		System.out.print(temp.key+"  ");
    	}
    	System.out.println();
    }
}
public class Teacher_1_15_MyLRUCache {
    public static void main(String[] args) {
      //维护插入顺序
      MyLRUCache<Integer, String> cache = new MyLRUCache<>(3);
      cache.put(1, "ajhd");
      cache.put(5, "fsd");
      cache.put(12, "ref");
      cache.show();//应该1 5  12
      System.out.println(cache.get(5));//成功则返回8
      cache.show(); //应该1 12 5
      cache.put(10, "gdfs");
      cache.show();//应该12 5 10
      //如何打印双向链表里的顺序
      System.out.println(cache.get(1));
    }
}

到此这篇关于Java中的LinkedHashMap及LRU缓存机制详解的文章就介绍到这了,更多相关LinkedHashMap及LRU缓存机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java修饰符abstract与static及final的精华总结

    Java修饰符abstract与static及final的精华总结

    abstract、static、final三个修饰符是经常会使用的,对他们的概念必须非常清楚,弄混了会产生些完全可以避免的错误,比如final和abstract不能一同出现,static和abstract不能一同出现,下面我们来详细了解
    2022-04-04
  • java编程多线程并发处理实例解析

    java编程多线程并发处理实例解析

    这篇文章主要介绍了java编程多线程并发处理实例解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • java中Base64字符串出现不合法字符的问题解决

    java中Base64字符串出现不合法字符的问题解决

    非法的base64数据可能导致编码或解码过程出错,本文主要介绍了java中Base64字符串出现不合法字符的问题解决,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • 使用注解进行Spring开发的全过程

    使用注解进行Spring开发的全过程

    使用注解(Annotation)是一种在代码级别进行说明和标记的技术,它从JDK 5.0开始引入,并在现代Java开发中得到了广泛应用,本文将详细介绍Spring框架中常用的注解及示例,帮助开发者快速掌握Spring注解开发的要点和技巧,需要的朋友可以参考下
    2023-11-11
  • javax.validation包里@NotNull等注解的使用方式

    javax.validation包里@NotNull等注解的使用方式

    这篇文章主要介绍了javax.validation包里@NotNull等注解的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • Java jvm中Code Cache案例详解

    Java jvm中Code Cache案例详解

    这篇文章主要介绍了Java jvm中Code Cache案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • SpringBoot使用@Cacheable注解实现缓存功能流程详解

    SpringBoot使用@Cacheable注解实现缓存功能流程详解

    最近一直再学Spring Boot,在学习的过程中也有过很多疑问。为了解答自己的疑惑,也在网上查了一些资料,以下是对@Cacheable注解的一些理解
    2023-01-01
  • springBoot项目集成quartz开发定时任务案例及注意事项

    springBoot项目集成quartz开发定时任务案例及注意事项

    这篇文章主要介绍了springBoot项目集成quartz开发定时任务案例及注意事项,这些功能的主要接口(API)是Scheduler接口。它提供了简单的操作,例如:将任务纳入日程或者从日程中取消,开始/停止/暂停日程进度,需要的朋友可以参考下
    2022-06-06
  • SpringCloud项目中Feign组件添加请求头所遇到的坑及解决

    SpringCloud项目中Feign组件添加请求头所遇到的坑及解决

    这篇文章主要介绍了SpringCloud项目中Feign组件添加请求头所遇到的坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • Java成员变量与局部变量(动力节点Java学院整理)

    Java成员变量与局部变量(动力节点Java学院整理)

    这篇文章主要介绍了Java成员变量与局部变量的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-04-04

最新评论