基于LinkedHashMap实现LRU缓存调度算法原理

引言

本文就 《基于LinkedHashMap实现LRU缓存调度算法原理及应用 》一文作为材料,记录一些常见问题,备忘。

延伸出两道常见的Java面试题:

  1. 插入Entry节点到table表的链表中时,Hashmap 和LinkedHashmap使用头茶法还是尾茶法?遍历map的时候,Entry.Entryset()获取的set集合,是按照从头到尾还是从尾到头的顺序存储的?
  2. 实现LRU算法最合适的数据结构?

如果读者可以打出来,不用继续看下边的资料了。初学者请继续阅读。相信你读完之后可以找到问题的答案。

LinkedHashMap基础

LinkedHashMap继承了HashMap底层是通过Hash表+单向链表实现Hash算法,内部自己维护了一套元素访问顺序的列表。

Java代码  

  1. /**
  2. * The head of the doubly linked list.
  3. */
  4. private transient Entry<K,V> header;
  5. .....
  6. /**
  7. * LinkedHashMap entry.
  8. */
  9. private static class Entry<K,V> extends HashMap.Entry<K,V> {
  10. // These fields comprise the doubly linked list used for iteration.
  11. Entry<K,V> before, after;

HashMap构造函数中回调了子类的init方法实现对元素初始化

Java代码  

  1. void init() {
  2. header = new Entry<K,V>(-1, null, null, null);
  3. header.before = header.after = header;
  4. }

LinkedHashMap中有一个属性可以执行列表元素的排序算法

Java代码  

  1. /**
  2. * The iteration ordering method for this linked hash map: <tt>true</tt>
  3. * for access-order, <tt>false</tt> for insertion-order.
  4. *
  5. * @serial
  6. */
  7. private final boolean accessOrder;

accessOrder为true使用访问顺序排序,false使用插入顺序排序那么在哪里可以设置这个值。

Java代码  

  1. /**
  2. * Constructs an empty <tt>LinkedHashMap</tt> instance with the
  3. * specified initial capacity, load factor and ordering mode.
  4. *
  5. * @param  initialCapacity the initial capacity.
  6. * @param  loadFactor      the load factor.
  7. * @param  accessOrder     the ordering mode - <tt>true</tt> for
  8. *         access-order, <tt>false</tt> for insertion-order.
  9. * @throws IllegalArgumentException if the initial capacity is negative
  10. *         or the load factor is nonpositive.
  11. */
  12. public LinkedHashMap(int initialCapacity,
  13. float loadFactor,
  14. boolean accessOrder) {
  15. super(initialCapacity, loadFactor);
  16. this.accessOrder = accessOrder;
  17. }

LRU算法

使用有访问顺序排序方式实现LRU,那么哪里LinkedHashMap是如何实现LRU的呢?

Java代码  

  1. //LinkedHashMap方法
  2. public V get(Object key) {
  3. Entry<K,V> e = (Entry<K,V>)getEntry(key);
  4. if (e == null)
  5. return null;
  6. e.recordAccess(this);
  7. return e.value;
  8. }
  9. //HashMap方法
  10. public V put(K key, V value) {
  11. if (key == null)
  12. return putForNullKey(value);
  13. int hash = hash(key.hashCode());
  14. int i = indexFor(hash, table.length);
  15. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  16. Object k;
  17. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  18. V oldValue = e.value;
  19. e.value = value;
  20. e.recordAccess(this);
  21. return oldValue;
  22. }
  23. }
  24. modCount++;
  25. addEntry(hash, key, value, i);
  26. return null;
  27. }

当调用get或者put方法的时候,如果K-V已经存在,会回调Entry.recordAccess()方法

我们再看一下LinkedHashMap的Entry实现

Java代码  

  1. /**
  2. * This method is invoked by the superclass whenever the value
  3. * of a pre-existing entry is read by Map.get or modified by Map.set.
  4. * If the enclosing Map is access-ordered, it moves the entry
  5. * to the end of the list; otherwise, it does nothing.
  6. */
  7. void recordAccess(HashMap<K,V> m) {
  8. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
  9. if (lm.accessOrder) {
  10. lm.modCount++;
  11. remove();
  12. addBefore(lm.header);
  13. }
  14. }
  15. /**
  16. * Remove this entry from the linked list.
  17. */
  18. private void remove() {
  19. before.after = after;
  20. after.before = before;
  21. }
  22. /**
  23. * Insert this entry before the specified existing entry in the list.
  24. */
  25. private void addBefore(Entry<K,V> existingEntry) {
  26. after  = existingEntry;
  27. before = existingEntry.before;
  28. before.after = this;
  29. after.before = this;
  30. }

recordAccess方法会accessOrder为true会先调用remove清楚的当前首尾元素的指向关系,之后调用addBefore方法,将当前元素加入header之前。

当有新元素加入Map的时候会调用Entry的addEntry方法,会调用removeEldestEntry方法,这里就是实现LRU元素过期机制的地方,默认的情况下removeEldestEntry方法只返回false表示元素永远不过期。

Java代码  

  1. /**
  2. * This override alters behavior of superclass put method. It causes newly
  3. * allocated entry to get inserted at the end of the linked list and
  4. * removes the eldest entry if appropriate.
  5. */
  6. void addEntry(int hash, K key, V value, int bucketIndex) {
  7. createEntry(hash, key, value, bucketIndex);
  8. // Remove eldest entry if instructed, else grow capacity if appropriate
  9. Entry<K,V> eldest = header.after;
  10. if (removeEldestEntry(eldest)) {
  11. removeEntryForKey(eldest.key);
  12. } else {
  13. if (size >= threshold)
  14. resize(2 * table.length);
  15. }
  16. }
  17. /**
  18. * This override differs from addEntry in that it doesn‘t resize the
  19. * table or remove the eldest entry.
  20. */
  21. void createEntry(int hash, K key, V value, int bucketIndex) {
  22. HashMap.Entry<K,V> old = table[bucketIndex];
  23. Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
  24. table[bucketIndex] = e;
  25. e.addBefore(header);
  26. size++;
  27. }
  28. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
  29. return false;
  30. }

基本的原理已经介绍完了,那基于LinkedHashMap我们看一下是该如何实现呢?

Java代码  

import java.util.LinkedHashMap;

public class URLLinkedListHashMap<K, V> extends LinkedHashMap<K, V> {

	/**
	 *
	 */
	private static final long serialVersionUID = 1L;

	/** 最大数据存储容量 */
    private static final int  LRU_MAX_CAPACITY     = 1024;  

    /** 存储数据容量  */
    private int               capacity;  

    /**
     * 默认构造方法
     */
    public URLLinkedListHashMap() {
        super();
    }  

    /**
     * 带参数构造方法
     * @param initialCapacity   容量
     * @param loadFactor        装载因子
     * @param isLRU             是否使用lru算法,true:使用(按方案顺序排序);false:不使用(按存储顺序排序)
     */
    public URLLinkedListHashMap(int initialCapacity, float loadFactor, boolean isLRU) {
        super(initialCapacity, loadFactor, isLRU);
        capacity = LRU_MAX_CAPACITY;
    }  

    public URLLinkedListHashMap(int initialCapacity, float loadFactor, boolean isLRU,int lruCapacity) {
        super(initialCapacity, loadFactor, isLRU);
        this.capacity = lruCapacity;
    } 

    @Override
    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
    	// TODO Auto-generated method stub
    	return super.removeEldestEntry(eldest);
    }

}

测试代码:

Java代码  

import java.util.LinkedHashMap;
import java.util.Map.Entry;

public class LRUTest {

    public static void main(String[] args) {

        LinkedHashMap<String, String> map = new URLLinkedListHashMap(16, 0.75f, false);
        map.put("a", "a"); //a  a
        map.put("b", "b"); //a  a b
        map.put("c", "c"); //a  a b c
        map.put("a", "a"); //   b c a
        map.put("d", "d"); //b  b c a d
        map.put("a", "a"); //   b c d a
        map.put("b", "b"); //   c d a b
        map.put("f", "f"); //c  c d a b f
        map.put("g", "g"); //c  c d a b f g

        map.get("d"); //c a b f g d
        for (Entry<String, String> entry : map.entrySet()) {
            System.out.print(entry.getValue() + ", ");
        }

        System.out.println();

        map.get("a"); //c b f g d a
        for (Entry<String, String> entry : map.entrySet()) {
            System.out.print(entry.getValue() + ", ");
        }
        System.out.println();

        map.get("c"); //b f g d a c
        for (Entry<String, String> entry : map.entrySet()) {
            System.out.print(entry.getValue() + ", ");
        }
        System.out.println();

        map.get("b"); //f g d a c b
        for (Entry<String, String> entry : map.entrySet()) {
            System.out.print(entry.getValue() + ", ");
        }
        System.out.println();

        map.put("h", "h"); //f  f g d a c b h
        for (Entry<String, String> entry : map.entrySet()) {
            System.out.print(entry.getValue() + ", ");
        }
        System.out.println();
    }

}

答案:

  1. 插入Entry节点到table表的链表中时,Hashmap 和LinkedHashmap使用头茶法。遍历map的时候,Entry.Entryset()获取的set集合,是按照从尾到头的顺序存储的,采用FIFO原理打印。
  2. 实现LRU算法最合适的数据结构是LinkedHashmap

部分转自: http://woming66.iteye.com/blog/1284326

时间: 2024-08-08 01:36:19

基于LinkedHashMap实现LRU缓存调度算法原理的相关文章

Java集合详解5:深入理解LinkedHashMap和LRU缓存

Java集合详解5:深入理解LinkedHashMap和LRU缓存 今天我们来深入探索一下LinkedHashMap的底层原理,并且使用linkedhashmap来实现LRU缓存. 具体代码在我的GitHub中可以找到 https://github.com/h2pl/MyTech 文章首发于我的个人博客: https://h2pl.github.io/2018/05/11/collection5 更多关于Java后端学习的内容请到我的CSDN博客上查看:https://blog.csdn.net

手写一个自己的LocalCache - 基于LinkedHashMap实现LRU

功能目标 实现一个全局范围的LocalCache,各个业务点使用自己的Namespace对LocalCache进行逻辑分区,所以在LocalCache中进行读写采用的key为(namespace+(分隔符)+数据key),如存在以下的一对keyValue :  NameToAge,Troy -> 23 .要求LocalCache线程安全,且LocalCache中总keyValue数量可控,提供清空,调整大小,dump到本地文件等一系列操作. 用LinkedHashMap实现LRU Map Lin

LinkedHashMap实现LRU缓存算法

LinkedHashMap的get()方法除了返回元素之外还可以把被访问的元素放到链表的底端,这样一来每次顶端的元素就是remove的元素. 构造函数如下: public LinkedHashMap (int initialCapacity, float loadFactor, boolean accessOrder): initialCapacity   初始容量 loadFactor    加载因子,一般是 0.75f accessOrder   false基于插入顺序,true 基于访问顺

LRU缓存实现(Java)

LRU Cache的LinkedHashMap实现 LRU Cache的链表+HashMap实现 LinkedHashMap的FIFO实现 调用示例 LRU是Least Recently Used 的缩写,翻译过来就是"最近最少使用",LRU缓存就是使用这种原理实现,简单的说就是缓存一定量的数据,当超过设定的阈值时就把一些过期的数据删除掉,比如我们缓存10000条数据,当数据小于10000时可以随意添加,当超过10000时就需要把新的数据添加进来,同时要把过期数据删除,以确保我们最大缓

用Linkedhashmap的LRU特性及SoftReference软引用构建二级缓存

LRU: least recently used(近期最少使用算法).LinkedHashMap构造函数可以指定其迭代顺序:LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) 设置accessOrder为true,则按照访问顺序迭代.当linkedhashmap调用put或者putall成功插入键值时,会调用removeEldestEntry方法,根据该方法的返回值决定是否删除最老对象(accessO

LRU缓存介绍与实现 (Java)

引子: 我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系 了,要再次联系那位朋友的时候,我们又不得不求助电话本,但是,通过电话本查找还是很费时间的.但是,我们大脑能够记住的东西是一定的,我们只能记住自己 最熟悉的,而长时间不熟悉的自然就忘记了. 其实,计算机也用到了同样的一个概念,我们用缓存来存放以前读取的数据,而不是直接丢掉,这样,再次读取的时候,可以直接在缓存里面取,而不用再重 新查找一遍,这样系统的反

LinkedHashMap 和 LRU算法实现

个人觉得LinkedHashMap 存在的意义就是为了实现 LRU 算法. public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> { public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.

转: LRU缓存介绍与实现 (Java)

引子: 我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本,但是,通过电话本查找还是很费时间的.但是,我们大脑能够记住的东西是一定的,我们只能记住自己最熟悉的,而长时间不熟悉的自然就忘记了. 其实,计算机也用到了同样的一个概念,我们用缓存来存放以前读取的数据,而不是直接丢掉,这样,再次读取的时候,可以直接在缓存里面取,而不用再重新查找一遍,这样系统的反应能力

LRU缓存算法与pylru

这篇写的略为纠结,算法原理.库都是现成的,我就调用了几个函数而已,这有啥好写的?不过想了想,还是可以介绍一下LRU算法的原理及简单的用法. LRU(Least Recently Used,最近最少使用)是一种内存页面置换算法.什么叫内存页面置换?我们知道,相对于内存的速度来讲,磁盘的速度是很慢的.我们需要查询数据的时候,不能每次都跑到磁盘去查,需要在内存里设置一块空间,把一些常用的数据放在这块空间里,以后查的时候就直接在这里查,而不必去磁盘,从而起到“加速”的作用.但是这块空间肯定是远远小于磁盘