Java中的LinkedHashMap及LRU缓存机制详解
作者:强钦钦
这篇文章主要介绍了Java中的LinkedHashMap及LRU缓存机制详解,LinkedHashMap继承自HashMap,它的多种操作都是建立在HashMap操作的基础上的,同HashMap不同的是,LinkedHashMap维护了一个Entry的双向链表,保证了插入的Entry中的顺序,需要的朋友可以参考下
LinkedHashMap
- 维持插入顺序;如 (1,“a”) (2, “b”)(先插的先访问)
- 维持访问顺序(将最近访问的数据移到链表的尾部 LRU思想 afterNodeAccess(里面处理了Entry的before after属性))
- 主要是底层维护了一个双向链表
- 不能被克隆和序列化(HashMap可以)
- 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缓存机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!