关闭

Java实现LRU缓存

标签: javaluceneredis缓存优化
481人阅读 评论(1) 收藏 举报
分类:

1.Cache

Cache对于代码系统的加速与优化具有极大的作用,对于码农来说是一个很熟悉的概念。可以说,你在内存中new 了一个一段空间(比方说数组,list)存放一些冗余的结果数据,并利用这些数据完成了以空间换时间的优化目的,你就已经使用了cache。
有服务级的缓存框架,如memcache,redis等。其实,很多时候,我们在自己同一个服务内,或者单个进程内也需要缓存,例如,lucene就对搜索做了缓存,而无须依赖外界。那么,我们如何实现我们自己的缓存?还要带自动失效的,最好还是LRU(Least Recently Used)。

当你思考怎么去实现,你可能会想得很远。为了LRU,需要把刚使用的数据存入栈,或者纪录每个数据最近使用的时间,再来的定时扫描失效的线程….其实,java本身就已经为我们提供了LRU Cache很好的实现,即LinkedHashMap。

2.LinkedHashMap分析

很多没有去细究过其内部实现的人,只是将其当作一个普通的hashMap来对待。LinkedHashMap是一个双向链表,加上HashTable的实现。表现出来与普通HashMap的一个区别就是LinkedHashMap会记录存入其中的数据的顺序,并能按顺取出。
为了实现,一个hash表,自然应该先申请在一片连续的内存空间上。当需要存入数据的时候,根据相应的hash值存入。而LinkedHashMap在这个基础上,为每个entry设置了before与after属性,形了一个双向链表,记录了他们put进入的前后顺序。

不仅如此,每当通过get来获得某个元素后,get方法内部,会在最后通过afterNodeAccess方法来调整链表的指向:

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.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;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

上述代码将Node e移至了双向链表的未尾。而在方法afterNodeInsertion中,只要满足条件,便移除最老的数据,即链表的head。

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
} 

可见,当你为LinkedHashMap设置有限空间的时候,自然便完成了LRU Cache的效果。当然还有一个前提,你必须重写一个方法removeEldestEntry,返回true。表示空间已满时,删除最老的。

@Override
public boolean removeEldestEntry(Map.Entry<K, V> eldest){     
    return size()>capacity;        
}

3.线程安全的LRU Cache

如此,我们就获得了一个LRU缓存利器,满足了我们大多场景下的需求。但还有一个问题,它不是线程安全的。在多线程的情况下,你有可能需要对某些Cache做同步处理。这时候,你再找,可以看到java有ConcurrentHashMap的实现,但并不存在ConcurrentLinkedHashMap这样的类。
当然这个问题也不大,我们可以对再有的LinkedHashMap,再作封装,对get,put, 之类的方法加上同步操作。

目前,我们所用的处理,是直接采和google提供的guava包,这里面就提供了我们想要的ConcurrentLinkedHashMap。这样就可以很方便地实现一个线程安全。具体代码如下:

import java.util.Set;

  import com.googlecode.concurrentlinkedhashmap.Weighers;
  import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
  public class ConcurrentLRUCache<K, V> {
     public static final int                     DEFAULT_CONCURENCY_LEVEL = 32;

  private final ConcurrentLinkedHashMap<K, V> map;


  public ConcurrentLRUCache(int capacity) {
      this(capacity, DEFAULT_CONCURENCY_LEVEL);
  }

  public ConcurrentLRUCache(int capacity, int concurrency) {
      map = new ConcurrentLinkedHashMap.Builder<K, V>().weigher(Weighers.<V> singleton())
        .initialCapacity(capacity).maximumWeightedCapacity(capacity)
        .concurrencyLevel(concurrency).build();
  }

  public void put(K key, V value) {
      map.put(key, value);
  }

  public V get(K key) {
      V v = map.get(key);
      return v;
  }

  public V getInternal(K key) {
      return map.get(key);
  }

  public void remove(K key) {
      map.remove(key);
  }

  public long getCapacity() {
      return map.capacity();
  }


  public void updateCapacity(int capacity) {
      map.setCapacity(capacity);
  }


  public int getSize() {
      return map.size();
  }


  public void clear() {
      map.clear();
  }

  public Set<K> getKeySet() {
      return map.keySet();
  }
}
1
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

如何用LinkedHashMap实现LRU缓存算法

缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的。LRU这个算法就是把最近一次使用时间离现在时间最远的数据删除掉。先说说List:每次访问一个元素...
  • yangpl_tale
  • yangpl_tale
  • 2015-04-11 20:01
  • 1595

缓存淘汰算法--LRU算法(java代码实现)

LRU 缓存 算法
  • wangxilong1991
  • wangxilong1991
  • 2017-04-14 14:59
  • 1406

java动态缓存成长小纪(二)——缓存算法的实现:LRU、LFU、FIFO

缓存算法也叫作淘汰算法,主要是为了当JVM空间不足时,用来清理掉缓存的。那么要清理的话,我们先清理掉哪些缓存呢?按照正常人的思维,当然是接下来一段时间内不大可能用到的缓存啦!根据这个思路,我们需要做出...
  • u011680348
  • u011680348
  • 2015-08-14 11:17
  • 2851

LRU缓存介绍与实现 (Java)

引子: 我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电...
  • yzw19932010
  • yzw19932010
  • 2013-12-19 20:35
  • 704

Java实现LRU(最近最少使用)缓存

http://blog.csdn.net/liuzhenfeng/article/details/6254224 前几天去一个公司面试,面试官直接让上机写一个LRU缓存,当时写的很乱,现整理如下: ...
  • victoryckl
  • victoryckl
  • 2014-03-20 09:30
  • 1235

LRU缓存介绍与实现 (Java)

引子: 我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本...
  • renyisheng
  • renyisheng
  • 2015-06-11 10:46
  • 253

LRU缓存介绍与实现 (Java)

LRU缓存介绍与实现 (Java) 分类: 算法 (Algorithm)2011-11-26 06:51 6490人阅读 评论(1) 收藏 举报 javacacheinherita...
  • liangdingfeng
  • liangdingfeng
  • 2014-08-27 14:46
  • 241

LRU缓存实现(Java)

LRU Cache的LinkedHashMap实现LRU Cache的链表+HashMap实现LinkedHashMap的FIFO实现调用示例 LRU是Least Recently Used...
  • zhangsheXIN_
  • zhangsheXIN_
  • 2015-04-30 15:04
  • 224

LRU缓存介绍与实现 (Java)

引子: 我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电...
  • mydreamongo
  • mydreamongo
  • 2013-05-03 16:15
  • 686

LRU缓存介绍与实现 (Java)

引子: 我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本...
  • sd4015700
  • sd4015700
  • 2014-04-29 15:49
  • 357
    Java技术交流群:6128790
    微信
    编程技术学习交流群 :154514123
    微信
    个人资料
    • 访问:590134次
    • 积分:8283
    • 等级:
    • 排名:第2816名
    • 原创:311篇
    • 转载:34篇
    • 译文:2篇
    • 评论:110条
    最新评论