LC上有这么一道题让实现一个LRU,LRU如上描述就是一个有容量限制当容量满时会自动移除最后一次时间时间最晚的缓存结构。
想到Redis中的ZSET结构(主要是想到了昨天学的漏斗限流…),不过只能存储key不能存储value…
在Java中也有排序表的结构,可以使用一个TreeMap,key为一个HashMap,HashMap存储题目中的key和value,TreeMap的value为操作时间戳,每访问一次或者修改一次就更新。如果超出容量设定了就移除时间戳最靠前的那一个。
不过Java中有种更合适于干这事的结构,就是LinkedHashMap
只使用LinkedHashMap实现代码就是这样的。

class LRUCache {private LinkedHashMap<Integer,Integer> lhm;public LRUCache(int capacity) {this.lhm = new LinkedHashMap<Integer,Integer> (capacity,0.75f,true){@Overrideprotected  boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest){return size()>capacity;        }};}public int get(int key) {return lhm.getOrDefault(key,-1);}public void put(int key, int value) {lhm.put(key,value);}
}

不过面试如果遇到,面试官估计是想用双向链表+HashMap自己实现,当然硬核一点也阔以实现一个简易的HashMap(不过没必要,面试那么短的时间不可能写完的,除非能够全文默写滑稽)

class LRUCache_{private static class Node{Node pre,next;int value,key;Node(int val,int key){value = val;this.key = key;}}private int capacity;LRUCache_(int capacity){this.capacity = capacity;}private Map<Integer,Node> map = new HashMap<>();private Node head,tail;public void put(int key,int val){if(get(key)!=-1){map.get(key).value = val;}else {Node node = new Node(val, key);map.put(key, node);addTail(node);if (map.size() > capacity) {removehead();}}}public int get(int key){Node g = map.getOrDefault(key,null);if(g==null)return -1;else{setTail(g);return g.value;}}private void removehead(){Node frist;//1.解除head与它的next之间的链接if((frist=head.next)!=null){head.next = null;frist.pre = null;}//2.解除与map的连接map.remove(head.key);head = frist;}private void addTail(Node newTail){if(tail==null){head=newTail;}else {tail.next = newTail;newTail.pre = tail;}tail = newTail;}private void setTail(Node newTail) {Node p = newTail.pre,t = newTail.next;if(t==null){return;}if(p==null){t.pre = null;head = t;}else {p.next = t;t.pre = p;}tail.next = newTail;newTail.pre = tail;newTail.next = null;tail = newTail;}
}

LinkedHashMap

LinkedHashMap是HashMap的子类,不过它多了一个双向链表再将HashMap里的元素按照一定的顺序串起来。

先从签名下手,可以看出它是HashMap的子类,实现了Map接口

public class LinkedHashMap<K,V>extends HashMap<K,V>implements Map<K,V>

属性

再看属性。

双向链表头结点
transient LinkedHashMap.Entry<K,V> head;
双向链表尾节点
transient LinkedHashMap.Entry<K,V> tail;
链表的排序规则,true访问顺序,false插入顺序
final boolean accessOrder;

构造方法

然后是构造方法,一共有四个方法,一个空参,一个含初始容量大小,一个含初始容量和负载参数,一个含含初始容量和负载参数还有排序规则,最后就是传入一个Map转化为LinkedHashMap,由构造方法可见,排序规则默认为插入排序。

     public LinkedHashMap() {super();accessOrder = false;}public LinkedHashMap(int initialCapacity) {super(initialCapacity);accessOrder = false;}public LinkedHashMap(int initialCapacity, float loadFactor) {super(initialCapacity, loadFactor);accessOrder = false;}public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}public LinkedHashMap(Map<? extends K, ? extends V> m) {super();accessOrder = false;putMapEntries(m, false);}

Entry

从功能表现上来说,与HashMap的区别,使用双向链表将HashMap的元素串起来来保证一种顺序。
那么接着来看看这个链表结构是如何的吧。
Entry本身是HashMap链表结构的子类,需要知道的是无论是红黑树结构还是链表结构,都会维护一个链表,而且有意思的是HashMap中的TreeNode是继承了LinkedHashMap中的Entry

static class Entry<K,V> extends HashMap.Node<K,V> {Entry<K,V> before, after;Entry(int hash, K key, V value, Node<K,V> next) {super(hash, key, value, next);}}

查找

接着看一下查部分吧,调用的HashMap中的查找功能,HashMap中使用的查询先是将Hash值映射为桶的下标,然后分为四种情况,
1.索引到的桶是空的
2.索引到的桶的头结点就是目标值,
3.桶中的为链表,则顺序查找,直到要么找到要么完全遍历。
4.桶中的为树,采用三种方式,(1).hash值不相等,用hash值确定查找的方向(2)如果本身可比较,则用利用比较确定方向 (3)深搜。
如果找不到则返回null。
如果找到了,则判断是否用访问顺序作为排序标准,如果是,则改变节点的位置。
afterNodeAccess方法是HashMap中的方法,写于HashMap中主要是方便插入时能够将新插入的节点放入合适的顺序,不过HashMap中本身是个空方法,LinkedHashMap重写了它。
将这些方法置于HashMap中主要是为了LinkedHashMap留下后路,方便的在诸如插入等方法中,将插入的节点调整到末尾,最为最后插入的节点。这种设计模式就有点像Spring中的AOP,增强方法,不过这里是绑定死的。
那么为啥HashMap不在get中插入这个方法呢,因为插入方法,无论是访问模式还是插入模式,都需要将该节点移到最后,而查找只有在访问模式下才需要,并且模式的选择是在LHM中的,不应该将此扩大到HashMap中。

public V get(Object key) {Node<K,V> e;if ((e = getNode(hash(key), key)) == null)return null;if (accessOrder)afterNodeAccess(e);return e.value;}

接着让我们继续来看看afterNodeAccess的具体实现。
首先思考一下如果需要将一个节点(node)移动到尾部,需要进行哪些工作,1.断开原来与node之间的连接 2.并建立node前置与后置的双向连接 3.原tail的after指向node,将node的befor指向tail。4.然后再将tail设为node。

void afterNodeAccess(Node<K,V> e) { // move node to lastLinkedHashMap.Entry<K,V> last;if (accessOrder && (last = tail) != e) {LinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.after = null;如果node没有befor说明,node只可能为头结点。这时将head置为它的after节点。if (b == null)head = a;else建立b->a的连接b.after = a;if (a != null)如果a不是空的,双向连接完成a.before = b;else如果没有after,则将当前的临时尾结点设为e的befor。last = b;如果没有当前尾结点,则说明可能是第一个插入的元素if (last == null)head = p;else {如果不是第一个插入的元素,则将e的前置设为当前尾节点,并将当前尾结点的后置设为e。p.before = last;last.after = p;}尾结点引用指向e。tail = p;++modCount;}}

不过1和2代码的联立的。

put方法是直接调用HashMap里的实现,不过重写了newNode和newTreeNode方法,创建的都是Entry(TreeNode本身就是Entry的子类),而不是HashMap中原生的Node,HashMap的put方法的逻辑大概是,
1.定位桶
2.0 桶是否为空,是直接插入。
2.1.不为空,如果是链表,遍历链表如果未找到与之相等的key,则插入到尾部,满足树化条件(桶数量大于64,该桶元素数量大于8)就树化。如果找到与之相等的key,记录下该node
2.2.不为空,如果是树,定位节点可能在的位置查找,如果找到记录下该节点,未找到在底部插入,再重新平衡树。
3.0如果找到与之key相同的情况,更新该节点的值,返回原值
3.1.未找到,返回null。
而LinkeHashMap
就是在3.0时,因为有更改操作,必然需要更新尾节点为当前节点使用afterNodeAccess()方法。
在3.1时,会使用afterNodeInsertion方法,判断是否已经满足删除头结点的条件。
其中removeEldestEntry(first)函数需要自己重写,删除节点的条件,比如此题的的条件就是超出了容量。

void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;evict条件若为false则为创建模式,比如反序列化的时候。其他时候都是true。if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;removeNode(hash(key), key, null, false, true);}}

这里删除节点主题逻辑与HashMap一致。
1.定位桶,如该桶不为空
2.查找逻辑与查找部分类似就不赘述了
3.如果目标节点存在于map中,则删除它。
3.1.如果是树结构,则先在链表中查找(树本身是链表结构的子类),移除该节点,如果达到反树化要求(小于6)就无需后续操作,未达到则还需移除树上的连接,使用该节点的右结点的最左节点替代(如果只有单个节点就更简单了,直接替代),然后平衡树。
3.2.如果是链表结构,则遍历移除
4.返回被删除键的所属节点
在LinkedHashMap中,会在第4步“插入”(用插入不太对,因为本来就在那),afterNodeRemoval方法将该节点解链。
这个方法不用多解释一看就懂。

 void afterNodeRemoval(Node<K,V> e) { // unlinkLinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;p.before = p.after = null;if (b == null)head = a;elseb.after = a;if (a == null)tail = b;elsea.before = b;}

LRU使用LinkedHashMap实现(主要分析LinkedHashMap的原理)相关推荐

  1. 通过分析LinkedHashMap了解LRU

    我们都知道LRU是最近最少使用,根据数据的历史访问记录来进行淘汰数据的.其核心思想是如果数据最近被访问过,那么将来访问的几率也更高.在这里提一下,Redis缓存和MyBatis二级缓存更新策略算法中就 ...

  2. Java类集框架 —— LinkedHashMap源码分析

    前言 我们知道HashMap底层是采用数组+单向线性链表/红黑树来实现的,HashMap在扩容或者链表与红黑树转换过程时可能会改变元素的位置和顺序.如果需要保存元素存入或访问的先后顺序,那就需要采用L ...

  3. Dubbo 源码分析 - 自适应拓展原理

    1.原理 我在上一篇文章中分析了 Dubbo 的 SPI 机制,Dubbo SPI 是 Dubbo 框架的核心.Dubbo 中的很多拓展都是通过 SPI 机制进行加载的,比如 Protocol.Clu ...

  4. AbstractQueuedSynchronizer 原理分析 - Condition 实现原理

    1. 简介 Condition是一个接口,AbstractQueuedSynchronizer 中的ConditionObject内部类实现了这个接口.Condition声明了一组等待/通知的方法,这 ...

  5. bfc是什么_全面分析总结BFC原理及实践

    前言 经常在面试中被问到"如何清除浮动?"."为什么 overflow: hidden 可以清除浮动?"等等比较基础的问题.虽然这些题目案在各种写面试题的文章中 ...

  6. Android 4.4 NotificationManagerService使用详解与原理分析(二)__原理分析

    前置文章: <Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解> 转载请务必注明出处:http://blog. ...

  7. 手撸spring源码分析IOC实现原理

    手撸spring源码分析IOC实现原理 文章出处:https://github.com/fuzhengwei/small-spring 根据小付哥的手撸spring核心源码一步步学习出来的结果收货总结 ...

  8. Vue3源码分析之打包原理

    Vue3源码分析之打包原理 如果之前你已经看过我的<Vue3源码分析之入门>,那么你可以直接阅读此篇文章 Vue3源码分析之入门 一.配置环境 1. 全局安装yarn Monorepo 管 ...

  9. Wireshark抓包分析交换机工作原理

    [实验名称] 交换机工作原理 [实验目的] 1.熟悉Linux虚拟网络环境: 2.熟悉Linux中network namespace的基本操作: 3.熟悉Linux中虚拟以太网设备Tap和veth p ...

最新文章

  1. IIS 伪静态配置(安装ISAPI_Rewrite配置)
  2. 「SAP技术」SAP WM 如何根据TR号码查询TO号码?
  3. ECMALL SEO 问题的解决方法
  4. rs485数据线接反_再拆一个RS232-RS485通信接口转换器
  5. LeetCode Add Two Numbers II(栈)
  6. 实用make最佳实践
  7. java将数字替换为空_将数字替换为java中正确位置的单词
  8. kalilinux安装qt_Kali Linux 安装和搜狗输入法的安装
  9. Data Structures(Chapter 3 of The Algorithm Design Manual)
  10. 芬朗DSP电脑调音软件
  11. Android 使用反射调用StorageManager中 Hide方法getVolumeList、getVolumeState
  12. 【温度检测】基于matlab GUI热红外图像温度检测系统【含Matlab源码 1920期】
  13. android root写入文件,android中root用户无法往某些目录写入文件解决方法
  14. 李宏毅2022机器学习hw5
  15. 为什么 Go 语言能在中国这么火?
  16. Swift --- 下标(subscript)
  17. 阿里云 企业邮箱域名解析(DNS)
  18. 智能照明控制系统 在医院中的设计和应用
  19. 混合模式程序集是针对“v1.1.4322”版的执行时生成的,在没有配置其它信息的情况下,无法在 4.0 执行时中载入该程序集。...
  20. 配备Apple T2 安全芯片的 Mac 机型及T2芯片mac电脑U盘装系统教程

热门文章

  1. 【TWS API使用教程3】---如何使用TWS API从盈透证券中设置contract及获取contract的信息?
  2. 基于泰勒级数展开求余弦函数值
  3. VirusTotal智能搜索
  4. 给程序员的十条沟通技巧建议【奥利给】
  5. 37所国家示范性软件学院
  6. 心田花开教学让你快速掌握心田上的百合花写法与修辞手法运用
  7. 解决联想拯救者显卡花屏
  8. 解决ValueError: bad input shape (xxxx, x)
  9. AutoCAD.Net开发问题之:层表事件的响应(续)
  10. Lanczos方法计算严格对角化格林函数