`
eric_hwp
  • 浏览: 126059 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

[转]LRU缓存介绍与实现 (Java) .

 
阅读更多

引子:

我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本,但是,通过电话本查找还是很费时间的。但是,我们大脑能够记住的东西是一定的,我们只能记住自己最熟悉的,而长时间不熟悉的自然就忘记了。

其实,计算机也用到了同样的一个概念,我们用缓存来存放以前读取的数据,而不是直接丢掉,这样,再次读取的时候,可以直接在缓存里面取,而不用再重新查找一遍,这样系统的反应能力会有很大提高。但是,当我们读取的个数特别大的时候,我们不可能把所有已经读取的数据都放在缓存里,毕竟内存大小是一定的,我们一般把最近常读取的放在缓存里(相当于我们把最近联系的朋友的姓名和电话放在大脑里一样)。现在,我们就来研究这样一种缓存机制。

LRU缓存:

LRU缓存利用了这样的一种思想。LRU是Least Recently Used 的缩写,翻译过来就是“最近最少使用”,也就是说,LRU缓存把最近最少使用的数据移除,让给最新读取的数据。而往往最常读取的,也是读取次数最多的,所以,利用LRU缓存,我们能够提高系统的performance.

实现:

要实现LRU缓存,我们首先要用到一个类 LinkedHashMap。 用这个类有两大好处:一是它本身已经实现了按照访问顺序的存储,也就是说,最近读取的会放在最前面,最最不常读取的会放在最后(当然,它也可以实现按照插入顺序存储)。第二,LinkedHashMap本身有一个方法用于判断是否需要移除最不常读取的数,但是,原始方法默认不需要移除(这是,LinkedHashMap相当于一个linkedlist),所以,我们需要override这样一个方法,使得当缓存里存放的数据个数超过规定个数后,就把最不常用的移除掉。LinkedHashMap的API写得很清楚,推荐大家可以先读一下。

要基于LinkedHashMap来实现LRU缓存,我们可以选择inheritance, 也可以选择 delegation, 我更喜欢delegation。基于delegation的实现已经有人写出来了,而且写得很漂亮,我就不班门弄斧了。代码如下:

 

  1. import java.util.LinkedHashMap;  
  2. import java.util.Collection;  
  3. import java.util.Map;  
  4. import java.util.ArrayList;  
  5.   
  6. /** 
  7. * An LRU cache, based on <code>LinkedHashMap</code>. 
  8. * 
  9. * <p> 
  10. * This cache has a fixed maximum number of elements (<code>cacheSize</code>). 
  11. * If the cache is full and another entry is added, the LRU (least recently used) entry is dropped. 
  12. * 
  13. * <p> 
  14. * This class is thread-safe. All methods of this class are synchronized. 
  15. * 
  16. * <p> 
  17. * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br> 
  18. * Multi-licensed: EPL / LGPL / GPL / AL / BSD. 
  19. */  
  20. public class LRUCache<K,V> {  
  21.   
  22. private static final float   hashTableLoadFactor = 0.75f;  
  23.   
  24. private LinkedHashMap<K,V>   map;  
  25. private int                  cacheSize;  
  26.   
  27. /** 
  28. * Creates a new LRU cache. 
  29. * @param cacheSize the maximum number of entries that will be kept in this cache. 
  30. */  
  31. public LRUCache (int cacheSize) {  
  32.    this.cacheSize = cacheSize;  
  33.    int hashTableCapacity = (int)Math.ceil(cacheSize / hashTableLoadFactor) + 1;  
  34.    map = new LinkedHashMap<K,V>(hashTableCapacity, hashTableLoadFactor, true) {  
  35.       // (an anonymous inner class)   
  36.       private static final long serialVersionUID = 1;  
  37.       @Override protected boolean removeEldestEntry (Map.Entry<K,V> eldest) {  
  38.          return size() > LRUCache.this.cacheSize; }}; }  
  39.   
  40. /** 
  41. * Retrieves an entry from the cache.<br> 
  42. * The retrieved entry becomes the MRU (most recently used) entry. 
  43. * @param key the key whose associated value is to be returned. 
  44. * @return    the value associated to this key, or null if no value with this key exists in the cache. 
  45. */  
  46. public synchronized V get (K key) {  
  47.    return map.get(key); }  
  48.   
  49. /** 
  50. * Adds an entry to this cache. 
  51. * The new entry becomes the MRU (most recently used) entry. 
  52. * If an entry with the specified key already exists in the cache, it is replaced by the new entry. 
  53. * If the cache is full, the LRU (least recently used) entry is removed from the cache. 
  54. * @param key    the key with which the specified value is to be associated. 
  55. * @param value  a value to be associated with the specified key. 
  56. */  
  57. public synchronized void put (K key, V value) {  
  58.    map.put (key, value); }  
  59.   
  60. /** 
  61. * Clears the cache. 
  62. */  
  63. public synchronized void clear() {  
  64.    map.clear(); }  
  65.   
  66. /** 
  67. * Returns the number of used entries in the cache. 
  68. * @return the number of entries currently in the cache. 
  69. */  
  70. public synchronized int usedEntries() {  
  71.    return map.size(); }  
  72.   
  73. /** 
  74. * Returns a <code>Collection</code> that contains a copy of all cache entries. 
  75. * @return a <code>Collection</code> with a copy of the cache content. 
  76. */  
  77. public synchronized Collection<Map.Entry<K,V>> getAll() {  
  78.    return new ArrayList<Map.Entry<K,V>>(map.entrySet()); }  
  79.   
  80. // end class LRUCache   
  81. ------------------------------------------------------------------------------------------  
  82. // Test routine for the LRUCache class.   
  83. public static void main (String[] args) {  
  84.    LRUCache<String,String> c = new LRUCache<String, String>(3);  
  85.    c.put ("1""one");                           // 1   
  86.    c.put ("2""two");                           // 2 1   
  87.    c.put ("3""three");                         // 3 2 1   
  88.    c.put ("4""four");                          // 4 3 2   
  89.    if (c.get("2") == nullthrow new Error();    // 2 4 3   
  90.    c.put ("5""five");                          // 5 2 4   
  91.    c.put ("4""second four");                   // 4 5 2   
  92.    // Verify cache content.   
  93.    if (c.usedEntries() != 3)              throw new Error();  
  94.    if (!c.get("4").equals("second four")) throw new Error();  
  95.    if (!c.get("5").equals("five"))        throw new Error();  
  96.    if (!c.get("2").equals("two"))         throw new Error();  
  97.    // List cache content.   
  98.    for (Map.Entry<String, String> e : c.getAll())  
  99.       System.out.println (e.getKey() + " : " + e.getValue()); }  
import java.util.LinkedHashMap;
import java.util.Collection;
import java.util.Map;
import java.util.ArrayList;

/**
* An LRU cache, based on <code>LinkedHashMap</code>.
*
* <p>
* This cache has a fixed maximum number of elements (<code>cacheSize</code>).
* If the cache is full and another entry is added, the LRU (least recently used) entry is dropped.
*
* <p>
* This class is thread-safe. All methods of this class are synchronized.
*
* <p>
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
* Multi-licensed: EPL / LGPL / GPL / AL / BSD.
*/
public class LRUCache<K,V> {

private static final float   hashTableLoadFactor = 0.75f;

private LinkedHashMap<K,V>   map;
private int                  cacheSize;

/**
* Creates a new LRU cache.
* @param cacheSize the maximum number of entries that will be kept in this cache.
*/
public LRUCache (int cacheSize) {
   this.cacheSize = cacheSize;
   int hashTableCapacity = (int)Math.ceil(cacheSize / hashTableLoadFactor) + 1;
   map = new LinkedHashMap<K,V>(hashTableCapacity, hashTableLoadFactor, true) {
      // (an anonymous inner class)
      private static final long serialVersionUID = 1;
      @Override protected boolean removeEldestEntry (Map.Entry<K,V> eldest) {
         return size() > LRUCache.this.cacheSize; }}; }

/**
* Retrieves an entry from the cache.<br>
* The retrieved entry becomes the MRU (most recently used) entry.
* @param key the key whose associated value is to be returned.
* @return    the value associated to this key, or null if no value with this key exists in the cache.
*/
public synchronized V get (K key) {
   return map.get(key); }

/**
* Adds an entry to this cache.
* The new entry becomes the MRU (most recently used) entry.
* If an entry with the specified key already exists in the cache, it is replaced by the new entry.
* If the cache is full, the LRU (least recently used) entry is removed from the cache.
* @param key    the key with which the specified value is to be associated.
* @param value  a value to be associated with the specified key.
*/
public synchronized void put (K key, V value) {
   map.put (key, value); }

/**
* Clears the cache.
*/
public synchronized void clear() {
   map.clear(); }

/**
* Returns the number of used entries in the cache.
* @return the number of entries currently in the cache.
*/
public synchronized int usedEntries() {
   return map.size(); }

/**
* Returns a <code>Collection</code> that contains a copy of all cache entries.
* @return a <code>Collection</code> with a copy of the cache content.
*/
public synchronized Collection<Map.Entry<K,V>> getAll() {
   return new ArrayList<Map.Entry<K,V>>(map.entrySet()); }

} // end class LRUCache
------------------------------------------------------------------------------------------
// Test routine for the LRUCache class.
public static void main (String[] args) {
   LRUCache<String,String> c = new LRUCache<String, String>(3);
   c.put ("1", "one");                           // 1
   c.put ("2", "two");                           // 2 1
   c.put ("3", "three");                         // 3 2 1
   c.put ("4", "four");                          // 4 3 2
   if (c.get("2") == null) throw new Error();    // 2 4 3
   c.put ("5", "five");                          // 5 2 4
   c.put ("4", "second four");                   // 4 5 2
   // Verify cache content.
   if (c.usedEntries() != 3)              throw new Error();
   if (!c.get("4").equals("second four")) throw new Error();
   if (!c.get("5").equals("five"))        throw new Error();
   if (!c.get("2").equals("two"))         throw new Error();
   // List cache content.
   for (Map.Entry<String, String> e : c.getAll())
      System.out.println (e.getKey() + " : " + e.getValue()); }


代码出自:http://www.source-code.biz/snippets/java/6.htm

 


在博客 http://gogole.iteye.com/blog/692103 里,作者使用的是双链表 + hashtable 的方式实现的。如果在面试题里考到如何实现LRU,考官一般会要求使用双链表 + hashtable 的方式。 所以,我把原文的部分内容摘抄如下:

 

双链表 + hashtable实现原理:

将Cache的所有位置都用双连表连接起来,当一个位置被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾则表示最近最少使用的Cache。当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘汰链表最后的部分即可。

 

  1. public class LRUCache {  
  2.       
  3.     private int cacheSize;  
  4.     private Hashtable<Object, Entry> nodes;//缓存容器   
  5.     private int currentSize;  
  6.     private Entry first;//链表头   
  7.     private Entry last;//链表尾   
  8.       
  9.     public LRUCache(int i) {  
  10.         currentSize = 0;  
  11.         cacheSize = i;  
  12.         nodes = new Hashtable<Object, Entry>(i);//缓存容器   
  13.     }  
  14.       
  15.     /** 
  16.      * 获取缓存中对象,并把它放在最前面 
  17.      */  
  18.     public Entry get(Object key) {  
  19.         Entry node = nodes.get(key);  
  20.         if (node != null) {  
  21.             moveToHead(node);  
  22.             return node;  
  23.         } else {  
  24.             return null;  
  25.         }  
  26.     }  
  27.       
  28.     /** 
  29.      * 添加 entry到hashtable, 并把entry  
  30.      */  
  31.     public void put(Object key, Object value) {  
  32.         //先查看hashtable是否存在该entry, 如果存在,则只更新其value   
  33.         Entry node = nodes.get(key);  
  34.           
  35.         if (node == null) {  
  36.             //缓存容器是否已经超过大小.   
  37.             if (currentSize >= cacheSize) {  
  38.                 nodes.remove(last.key);  
  39.                 removeLast();  
  40.             } else {  
  41.                 currentSize++;  
  42.             }             
  43.             node = new Entry();  
  44.         }  
  45.         node.value = value;  
  46.         //将最新使用的节点放到链表头,表示最新使用的.   
  47.         moveToHead(node);  
  48.         nodes.put(key, node);  
  49.     }  
  50.   
  51.     /** 
  52.      * 将entry删除, 注意:删除操作只有在cache满了才会被执行 
  53.      */  
  54.     public void remove(Object key) {  
  55.         Entry node = nodes.get(key);  
  56.         //在链表中删除   
  57.         if (node != null) {  
  58.             if (node.prev != null) {  
  59.                 node.prev.next = node.next;  
  60.             }  
  61.             if (node.next != null) {  
  62.                 node.next.prev = node.prev;  
  63.             }  
  64.             if (last == node)  
  65.                 last = node.prev;  
  66.             if (first == node)  
  67.                 first = node.next;  
  68.         }  
  69.         //在hashtable中删除   
  70.         nodes.remove(key);  
  71.     }  
  72.   
  73.     /** 
  74.      * 删除链表尾部节点,即使用最后 使用的entry 
  75.      */  
  76.     private void removeLast() {  
  77.         //链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)   
  78.         if (last != null) {  
  79.             if (last.prev != null)  
  80.                 last.prev.next = null;  
  81.             else  
  82.                 first = null;  
  83.             last = last.prev;  
  84.         }  
  85.     }  
  86.       
  87.     /** 
  88.      * 移动到链表头,表示这个节点是最新使用过的 
  89.      */  
  90.     private void moveToHead(Entry node) {  
  91.         if (node == first)  
  92.             return;  
  93.         if (node.prev != null)  
  94.             node.prev.next = node.next;  
  95.         if (node.next != null)  
  96.             node.next.prev = node.prev;  
  97.         if (last == node)  
  98.             last = node.prev;  
  99.         if (first != null) {  
  100.             node.next = first;  
  101.             first.prev = node;  
  102.         }  
  103.         first = node;  
  104.         node.prev = null;  
  105.         if (last == null)  
  106.             last = first;  
  107.     }  
  108.     /* 
  109.      * 清空缓存 
  110.      */  
  111.     public void clear() {  
  112.         first = null;  
  113.         last = null;  
  114.         currentSize = 0;  
  115.     }  
  116.   
  117. }  
  118.   
  119. class Entry {  
  120.     Entry prev;//前一节点   
  121.     Entry next;//后一节点   
  122.     Object value;//值   
  123.     Object key;//键   
  124. }  
public class LRUCache {
	
	private int cacheSize;
	private Hashtable<Object, Entry> nodes;//缓存容器
	private int currentSize;
	private Entry first;//链表头
	private Entry last;//链表尾
	
	public LRUCache(int i) {
		currentSize = 0;
		cacheSize = i;
		nodes = new Hashtable<Object, Entry>(i);//缓存容器
	}
	
	/**
	 * 获取缓存中对象,并把它放在最前面
	 */
	public Entry get(Object key) {
		Entry node = nodes.get(key);
		if (node != null) {
			moveToHead(node);
			return node;
		} else {
			return null;
		}
	}
	
	/**
	 * 添加 entry到hashtable, 并把entry 
	 */
	public void put(Object key, Object value) {
		//先查看hashtable是否存在该entry, 如果存在,则只更新其value
		Entry node = nodes.get(key);
		
		if (node == null) {
			//缓存容器是否已经超过大小.
			if (currentSize >= cacheSize) {
				nodes.remove(last.key);
				removeLast();
			} else {
				currentSize++;
			}			
			node = new Entry();
		}
		node.value = value;
		//将最新使用的节点放到链表头,表示最新使用的.
		moveToHead(node);
		nodes.put(key, node);
	}

	/**
	 * 将entry删除, 注意:删除操作只有在cache满了才会被执行
	 */
	public void remove(Object key) {
		Entry node = nodes.get(key);
		//在链表中删除
		if (node != null) {
			if (node.prev != null) {
				node.prev.next = node.next;
			}
			if (node.next != null) {
				node.next.prev = node.prev;
			}
			if (last == node)
				last = node.prev;
			if (first == node)
				first = node.next;
		}
		//在hashtable中删除
		nodes.remove(key);
	}

	/**
	 * 删除链表尾部节点,即使用最后 使用的entry
	 */
	private void removeLast() {
		//链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)
		if (last != null) {
			if (last.prev != null)
				last.prev.next = null;
			else
				first = null;
			last = last.prev;
		}
	}
	
	/**
	 * 移动到链表头,表示这个节点是最新使用过的
	 */
	private void moveToHead(Entry node) {
		if (node == first)
			return;
		if (node.prev != null)
			node.prev.next = node.next;
		if (node.next != null)
			node.next.prev = node.prev;
		if (last == node)
			last = node.prev;
		if (first != null) {
			node.next = first;
			first.prev = node;
		}
		first = node;
		node.prev = null;
		if (last == null)
			last = first;
	}
	/*
	 * 清空缓存
	 */
	public void clear() {
		first = null;
		last = null;
		currentSize = 0;
	}

}

class Entry {
	Entry prev;//前一节点
	Entry next;//后一节点
	Object value;//值
	Object key;//键
}

转载请注明出处:http://blog.csdn.net/beiyeqingteng

分享到:
评论

相关推荐

    LRU.rar_LRU_LRU java_lru.java

    在这个Java实现的LRU算法示例中,我们将深入探讨LRU的核心概念、如何在Java中实现以及可能的应用场景。 1. LRU算法原理: LRU算法的基本思想是:如果一个数据最近被访问过,那么将来被访问的可能性会更高。在内存...

    FIFO与LRU 算法实现(java).rar_FIFO Java_LRU_fifo

    在提供的文档"FIFO与LRU 算法实现(java).doc"中,可能包含了具体的代码实现和分析,而"www.pudn.com.txt"可能是文档的来源或补充信息。如果文档中的内容难以理解,建议寻求社区的帮助,或者尝试自己根据上述原理...

    Java实现LRU算法.zip

    通过以上代码,我们已经构建了一个简单的LRU缓存系统。在实际应用中,LRU算法不仅可以用于操作系统中的页面替换,还可以应用于数据库查询缓存、编程语言的内存管理(如Java的SoftReference和WeakReference)以及Web...

    LRU 缓存机制(java代码).docx

    在实现 LRU 缓存机制时,通常会采用哈希表(如 Java 中的 HashMap)与双向链表相结合的方式来实现。其中: - **哈希表**:用于快速查找和访问缓存中的元素。 - **双向链表**:用于维持缓存中元素的顺序,并支持高效...

    Java实现LRU缓存机制:深入解析与高效编码实践

    本文将详细介绍如何在Java中实现LRU缓存机制,包括其原理、实现方式以及编码实践。 LRU缓存机制是一种非常实用的缓存淘汰策略,它在很多应用场景中都有广泛的应用。在Java中实现LRU缓存,可以通过使用LinkedHashMap...

    LRU算法 java实现

    在Java中,我们可以使用数据结构如HashMap和LinkedList来实现LRU缓存。 首先,我们需要一个双向链表来保存缓存中的元素,链表的节点包含元素的键值对以及前后节点的引用。链表的顺序反映了元素的使用情况,最近使用...

    LRU_缓存策略_LRU_缓存.zip

    Java中,可以使用`java.util.LinkedHashMap`类,通过设置`accessOrder`为`true`来实现LRU缓存。Python中,有第三方库`functools.lru_cache`提供了内置的LRU缓存功能。在C++中,可以自定义数据结构来实现LRU缓存。 ...

    Lru.rar_LRU IN java_lru.java_replacement_simulation LRU_页面置换

    通过读取这些请求,调用`put()`和`get()`方法,我们可以观察LRU缓存的行为,验证其是否按照预期工作。 为了模拟页面置换,程序会跟踪每个页面的访问时间,并根据访问时间更新链表中的位置。当需要淘汰页面时,程序...

    Java实现LRU缓存的实例详解

    Java实现LRU缓存的实例详解 Java实现LRU缓存的实例详解是指通过Java语言实现一种高效的缓存机制,即LRU(Least Recently Used)缓存。LRU缓存是一种常用的缓存策略,其主要思想是将最近使用的数据存储在缓存中,...

    详解Java实现LRU缓存

    Java 实现 LRU 缓存有多种方法,本文将介绍使用 LinkedHashMap 实现 LRU 缓存的方法。 LRU 缓存原理 LRU 缓存的原理很简单,就是缓存一定量的数据,当超过设定的阈值时就把一些过期的数据删除掉。例如,我们缓存 ...

    Java实现简单LRU缓存算法

    这里的LRUCache类维护了一个双向链表和一个哈希表,用于实现LRU算法。具体实现细节如下: - get方法:首先在哈希表中查找对应的键值对,如果不存在,则返回-1;如果存在,则将对应的节点从双向链表中删除,并将其...

    LRU_缓存策略_LRU_缓存_源码.rar

    在编程中,Java的`java.util.LinkedHashMap`类就提供了LRU缓存的实现,可以通过设置构造函数的`accessOrder`参数为`true`来启用按访问顺序排序的功能。在Python中,可以使用`functools.lru_cache`装饰器实现LRU缓存...

    java-leetcode题解之第146题LRU缓存.zip

    总的来说,Java实现LRU缓存涉及的主要知识点包括:HashMap和LinkedList数据结构的理解与应用,自定义数据结构的设计,以及高效算法的实现。通过解决这道LeetCode题目,可以提升对这些概念的理解和实战能力。

    缓存淘汰算法之LRU

    LRU 算法的实现最常见的是使用一个链表保存缓存数据。详细算法实现如下: 1. 新数据插入到链表头部; 2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部; 3. 当链表满的时候,将链表尾部的数据丢弃。 3...

    LRU算法LRU算法LRU算法LRU算法

    在Java中,实现LRU算法可以使用`java.util.LinkedHashMap`类,它内部已经实现了LRU的逻辑。通过设置`accessOrder`参数为`true`,`LinkedHashMap`会按照访问顺序进行排序,从而实现LRU的效果。 总结一下,LRU算法是...

    Java实现简单LRU缓存机制的方法

    Java实现简单LRU缓存机制的方法 Java实现简单LRU缓存机制的方法是指通过Java语言实现的一种缓存机制,该机制可以根据最近最少使用的原则淘汰缓存中的数据。缓存机制的实现主要是通过哈希表和双向链表来实现的。 ...

    实现了LRU算法的缓存

    可以使用`synchronized`关键字或者`java.util.concurrent`包中的工具,如`ConcurrentHashMap`来实现线程安全的缓存操作。 6. **性能优化**: - 使用`WeakReference`或`SoftReference`可以避免内存泄漏,尤其是在...

    java 缓存 cache lru 实例

    java 缓存 cache lru 实例 java 缓存 cache lru 实例 java 缓存 cache lru 实例 java 缓存 cache lru 实例 java 缓存 cache lru 实例 java 缓存 cache lru 实例 java 缓存 cache lru 实例

    LRU.rar_LRU java

    下面将详细介绍LRU算法及其在Java中的实现。 **LRU算法原理** LRU算法的核心思想是:如果一个数据最近被访问过,那么将来被访问的可能性会更高。因此,当内存满时,最近最少使用的数据会被移除以腾出空间。具体步骤...

Global site tag (gtag.js) - Google Analytics