一、写随笔的原因:最近准备去朋友公司面试,他说让我看一下LRU算法,就此整理一下,方便以后的复习。

二、具体的内容:

1.简介:

  LRU是Least Recently Used的缩写,即最近最少使用。算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小”。

  这里不得不说i一下LFU(Least Frequently Used),其核心思想是“如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小

  LRU和LFU算法的区别是概括来说,LRU强调的是访问时间,而LFU则强调的是访问次数。

2.使用场景:

  一般来说它是用来作为一种缓存淘汰算法。

3.实现方法:

  方法一:用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。该方法有个致命的缺点就是需要不停地维护数据项的访问时间戳,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。

  方法二:可以使用链表来实现,分三步走:首先当新数据插入的时候,依次按顺序放到链表中; 其次查询数据时,已经存在链表中时(即缓存数据被访问),则将数据移到链表尾部;当链表满的时候,将链表顶部的数据丢弃。

4.Java中的实现代码:

  Java中最简单的LRU算法实现,就是利用LinkedHashMap,覆写其中的removeEldestEntry(Map.Entry)方法即可,内部就是使用的方法二实现的。代码如下:

  

  1. import java.util.LinkedHashMap;
  2.  
  3. import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;
    public class LRUTest {
  4.  
  5. static class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V> {
    //定义缓存的容量
    private int capacity;
    //带参数的构造器
    LRULinkedHashMap(int capacity){
    //如果accessOrder为true的话,则会把访问过的元素放在链表后面,放置顺序是访问的顺序(LinkedHashMap里面的get方法,当accessOrder为true,会走afterNodeAccess方法将节点移到最后)
    //如果accessOrder为flase的话,则按插入顺序来遍历
    super(16,0.75f,true);
    //传入指定的缓存最大容量
    this.capacity=capacity;
    }
    //实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素
    @Override
    public boolean removeEldestEntry(Map.Entry<K, V> eldest){
    return size()>capacity;
    }
    }
    //test
    public static void main(String[] args) {
    LRULinkedHashMap<String, Integer> testCache = new LRULinkedHashMap<>(3);
    testCache.put("A", 1);
    testCache.put("B", 2);
    testCache.put("C", 3);
    System.out.println(testCache.get("B"));
    System.out.println(testCache.get("A"));
    testCache.put("D", 4);
    System.out.println(testCache.get("D"));
    System.out.println(testCache.get("C"));
    }
    }

  输出结果:

  

  为何上述简单的代码LRU算法,具体要看LinkedHashMap里的put和get方法的源代码。

  1.首先是put()方法,这个在LinkedHashMap并没有重写,直接使用的是HashMap中的put()方法,这里解释一下上面的重写的removeEldestEntry()方法,在上次HashMap原理探究中,put()方法会调用内部的putVal()方法,putVal()方法中最后会调用一个afterNodeInsertion(evict);这个方法在LinkedHashMap里面有实现:

  1. void afterNodeInsertion(boolean evict) { // possibly remove eldest
  2. LinkedHashMap.Entry<K,V> first; //头结点
  3. if (evict && (first = head) != null && removeEldestEntry(first)) { //会调用我们重写的removeEldestEntry()
  4. K key = first.key;
  5. removeNode(hash(key), key, null, false, true); // 满足条件,这移除头结点
  6. }
  7. }

    会调用我们重写的removeEldestEntry()方法,当返回为true时,则会removeNode()方法移除链表的顶端元素,满足方法二中的第三个步骤。

  2.接下来看一下get()方法,这个在LinkedHashMap有重写,代码如下:

  1. public V get(Object key) {
  2. Node<K,V> e;
  3. if ((e = getNode(hash(key), key)) == null)
  4. return null;
  5. if (accessOrder)
  6. afterNodeAccess(e);
  7. return e.value;
  8. }

  这里会用到我们初始化时设置的accessOrder的值,我们当时设置的是ture,也就意味着会调用afterNodeAccess()方法,我们继续看afterNodeAccess的源码:

  1. void afterNodeAccess(Node<K,V> e) { // move node to last
  2. LinkedHashMap.Entry<K,V> last;
  3. if (accessOrder && (last = tail) != e) {
  4. LinkedHashMap.Entry<K,V> p =
  5. (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  6. p.after = null;
  7. if (b == null)
  8. head = a;
  9. else
  10. b.after = a;
  11. if (a != null)
  12. a.before = b;
  13. else
  14. last = b;
  15. if (last == null)
  16. head = p;
  17. else {
  18. p.before = last;
  19. last.after = p;
  20. }
  21. tail = p;
  22. ++modCount;
  23. }
  24. }

  源码中有块英文注释// move node to last,很明显是将查到的数据移动到链表的尾部。满足方法二中的第二个步骤。

  至于方法二中的第一个步骤,LinkedHashMap本身就是有顺序的插入,顺带分析下为啥它是如何实现有顺序插入的。由于它并没有单独实现put()方法,所以要看HashMap中的put()实现,还是参照上次HashMap原理探究 ,在put()方法会调用内部的putVal()方法,putVal()方法中添加新节点会调用newNode()方法,而LinkedHashMap对newNode进行了重写,代码如下:

  1. Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
  2. LinkedHashMap.Entry<K,V> p =
  3. new LinkedHashMap.Entry<K,V>(hash, key, value, e);
  4. linkNodeLast(p); // 这个方法就是将新节点顺序的插入到为节点的后面
  5. return p;
  6. }
  1.  
  1. linkNodeLast()方法:
  1. // link at the end of list
  2. private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
  3. LinkedHashMap.Entry<K,V> last = tail;
  4. tail = p;
  5. if (last == null)
  6. head = p;
  7. else {
  8. p.before = last;
  9. last.after = p;
  10. }
  11. }
  1.  

三、总结:

  本篇随笔主要就是LRU的介绍和在java中的快速实现,实现的代码相信网上非常的多,主要还是从源码角度分析了为何可以这样简单的实现,让我们能够更加理解LRU的基于链表的实现吧,同时也分析了下LinkedHashMap的部分源码实现,让大家能够更深入的理解吧。

  1.  

LRU算法介绍和在JAVA的实现及源码分析的更多相关文章

  1. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  2. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  3. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  4. Java并发指南10:Java 读写锁 ReentrantReadWriteLock 源码分析

    Java 读写锁 ReentrantReadWriteLock 源码分析 转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5 ...

  5. 【死磕 Java 集合】— ConcurrentSkipListMap源码分析

    转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...

  6. java线程池ThreadPoolExector源码分析

    java线程池ThreadPoolExector源码分析 今天研究了下ThreadPoolExector源码,大致上总结了以下几点跟大家分享下: 一.ThreadPoolExector几个主要变量 先 ...

  7. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  8. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  9. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

随机推荐

  1. Cortex-M3 异常中断向量表

    [Cortex-M3异常与中断] 支持10个系统异常和最多240个外部中断: 支持3个固定的高优先级和多达256级的可编程优先级,支持128级抢占: #0~15在Cortex-M3中定义,IRQ#0~ ...

  2. RNN 与 LSTM 的原理详解

    原文地址:https://blog.csdn.net/happyrocking/article/details/83657993 RNN(Recurrent Neural Network)是一类用于处 ...

  3. CentOS(Oracle_Linux)系统网卡配置文件参数详解

    Each physical and virtual network device on an Oracle Linux system has an associated configuration f ...

  4. iOS UItextView监听输入特定字符跳转页面选择选项返回

    今天有朋友问我一个需求的实现,于是自己写了一个Demo简单的实现了一下: 需求是: 1>比如: 检测用户输入"A"字符串,跳转页面选择选项,将选择的选项放置textView里 ...

  5. API接口设计的五大公共参数

    1.平台参数 2.操作系统参数 iOS.Android.PC等等 3.软件版本参数 4.udid号(设备唯一ID) 每个设备都会有一个唯一udid 5.渠道号 app软件从那个渠道下载

  6. 山东省第十届ACM省赛参赛后的学期总结

    5.11,5.12两天的济南之旅结束了,我也参加了人生中第一次正式的acm比赛,虽然是以友情队的身份,但是我依旧十分兴奋. 其实一直想写博客来增加自己的能力的,但是一直拖到现在,正赶上老师要求写一份总 ...

  7. PJzhang:360压缩的用户许可协议和隐私政策阅读

    猫宁!!! 参考链接:http://yasuo.360.cn/ 当我们安装一个软件,不管是手机上的还是电脑上的,很少会去看它们的用户许可协议和隐私政策,而有的时候软件甚至都不提醒我们还有用户许可协议和 ...

  8. 【VS开发】关于内存泄漏的调试

    没想到造成泄漏的原因是由于保存数据的线程因为事件阻塞在那里,此时要关闭OnClose的时候,这个挂起的线程爆出了内存泄漏,所以在关闭窗口之前,需要SetEvent(m_hSaveDataEvent); ...

  9. Java字节流文件封装

     /**  * 字节流封装方法  */ import java.io.FileInputStream; import java.io.FileNotFoundException; import jav ...

  10. hadoop3.1.2队列

    hapood3.1.2 capacity-scheduler.xml CDH6.2 在配置中输入fair,转成json格式看. yarn资源池配置: CDH--yarn--动态资源池配置 pool_d ...