LRU算法

Posted zmycoco2

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LRU算法相关的知识,希望对你有一定的参考价值。

LRU是Least Recently Used 的缩写,即“最近最少使用”,基于LRU算法实现的Cache机制简单地说就是缓存一定量的数据,当超过设定的阈值时就把一些过期的数据删除掉,比如我们缓存10000条数据,当数据小于10000时可以随意添加,当超过10000时就需要把新的数据添加进来,同时要把过期数据删除,以确保我们最大缓存10000条,那怎么确定删除哪条过期数据呢,采用LRU算法实现就是将最老的数据删除。Java里面实现LRU缓存通常有两种选择,一种是使用LinkedHashMap,一种是自己设计数据结构,使用链表+HashMap方式。

LinkedHashMap自身已经实现了顺序存储,默认情况下是按照元素的添加顺序存储,也可以启用按照访问顺序存储,即最近读取的数据放在最前面,最早读取的数据放在最后面,然后它还有一个判断是否删除最老数据的方法,默认是返回false,即不删除数据。

代码清单4-4所示示例是LinkedHashMap的一个构造函数,当参数accessOrder为true时,将会按照访问顺序排序,最后访问的放在最前,最早访问的放在后面。

代码清单4-4 LRU Cache的LinkedHashMap实现

public LinkedHashMap(int initialCapacity, float loadFactor, booleanaccessOrder)

        super(initialCapacity, loadFactor);

        this.accessOrder = accessOrder;

代码清单4-5所示赛马是LinkedHashMap自带的判断方法,判断是否删除最老的元素方法,默认返回false,即不删除老数据,我们要做的就是重写这个方法,当满足一定条件时删除老数据。

代码清单4-5 重写删除方法

protected booleanremoveEldestEntry(Map.Entry<K,V> eldest)

        return false;

采用inheritance方式实现比较简单,该方式实现了Map接口,在多线程环境使用时可以使用Collections.synchronizedMap()方法实现线程安全操作。

代码清单4-6 LRU缓存LinkedHashMap(inheritance)实现

importjava.util.LinkedHashMap;

import java.util.Map;

 

public classLRUCache2<K, V> extends LinkedHashMap<K, V>

    private final int MAX_CACHE_SIZE;

 

    public LRUCache2(int cacheSize)

        super((int) Math.ceil(cacheSize / 0.75)+ 1, 0.75f, true);

        MAX_CACHE_SIZE = cacheSize;

   

 

    @Override

    protected booleanremoveEldestEntry(Map.Entry eldest)

        return size() > MAX_CACHE_SIZE;

   

 

    @Override

    public String toString()

        StringBuilder sb = new StringBuilder();

        for (Map.Entry<K, V> entry :entrySet())

            sb.append(String.format("%s:%s", entry.getKey(), entry.getValue()));

       

        return sb.toString();

   

代码清单4-6的实现是比较标准的实现,在实际使用过程中这样写还是有些烦琐,更实用的方法是像代码清单4-7这样写,省去了单独新建一个类的麻烦。

代码清单4-7 LRU缓存LinkedHashMap(inheritance)实现改进版

final int cacheSize =100;

Map<String,String> map = new LinkedHashMap<String, String>((int) Math.ceil(cacheSize/ 0.75f) + 1, 0.75f, true)

    @Override

    protected booleanremoveEldestEntry(Map.Entry<String, String> eldest)

    return size() > cacheSize;

   

;

相比inheritance实现方式来说,delegation实现方式实现更加优雅一些,但是由于没有实现Map接口,所以线程同步就需要自己搞定了。

代码清单4-8 LRU缓存LinkedHashMap(delegation)实现

importjava.util.LinkedHashMap;

import java.util.Map;

import java.util.Set;

 

/**

 * Created by liuzhao on 14-5-13.

 */

public classLRUCache3<K, V>

 

    private final int MAX_CACHE_SIZE;

    private final float DEFAULT_LOAD_FACTOR =0.75f;

    LinkedHashMap<K, V> map;

 

    public LRUCache3(int cacheSize)

        MAX_CACHE_SIZE = cacheSize;

        //根据cacheSize和加载因子计算hashmap的capactiy,+1确保当达到cacheSize上限时不会触发hashmap的扩容,

        int capacity = (int)Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTOR) + 1;

        map = new LinkedHashMap(capacity,DEFAULT_LOAD_FACTOR, true)

            @Override

            protected booleanremoveEldestEntry(Map.Entry eldest)

                return size() >MAX_CACHE_SIZE;

           

        ;

   

 

    public synchronized void put(K key, Vvalue)

        map.put(key, value);

   

 

    public synchronized V get(K key)

        return map.get(key);

   

 

    public synchronized void remove(K key)

        map.remove(key);

   

 

    public synchronized Set<Map.Entry<K,V>> getAll()

        return map.entrySet();

   

 

    public synchronized int size()

        return map.size();

   

 

    public synchronized void clear()

        map.clear();

   

 

    @Override

    public String toString()

        StringBuilder sb = new StringBuilder();

        for (Map.Entry entry : map.entrySet())

            sb.append(String.format("%s:%s", entry.getKey(), entry.getValue()));

       

        return sb.toString();

   

注意,上面的实现方式是非线程安全的,若在多线程环境下使用需要在相关方法上添加synchronized以实现线程安全操作。

前面说过,除了LinkedHashMap方式以外,我们还有一种采用LRU Cache的链表+HashMap实现的方式,如代码清单4-9包含的代码所示。

代码清单4-9 LRU Cache的链表+HashMap实现

importjava.util.HashMap;

public classLRUCache1<K, V>

 

    private final int MAX_CACHE_SIZE;

    private Entry first;

    private Entry last;

 

    private HashMap<K, Entry<K, V>>hashMap;

 

    public LRUCache1(int cacheSize)

        MAX_CACHE_SIZE = cacheSize;

        hashMap = new HashMap<K, Entry<K,V>>();

   

 

    public void put(K key, V value)

        Entry entry = getEntry(key);

        if (entry == null)

            if (hashMap.size() >=MAX_CACHE_SIZE)

                hashMap.remove(last.key);

                removeLast();

           

            entry = new Entry();

            entry.key = key;

       

        entry.value = value;

        moveToFirst(entry);

        hashMap.put(key, entry);

   

 

    public V get(K key)

        Entry<K, V> entry =getEntry(key);

        if (entry == null) return null;

        moveToFirst(entry);

        return entry.value;

   

 

    public void remove(K key)

        Entry entry = getEntry(key);

        if (entry != null)

            if (entry.pre != null)entry.pre.next = entry.next;

            if (entry.next != null)entry.next.pre = entry.pre;

            if (entry == first) first =entry.next;

            if (entry == last) last =entry.pre;

       

        hashMap.remove(key);

   

 

    private void moveToFirst(Entry entry)

       if (entry == first) return;

        if (entry.pre != null) entry.pre.next =entry.next;

        if (entry.next != null) entry.next.pre= entry.pre;

        if (entry == last) last = last.pre;

 

        if (first == null || last == null)

            first = last = entry;

            return;

       

 

        entry.next = first;

        first.pre = entry;

        first = entry;

        entry.pre = null;

   

 

    private void removeLast()

        if (last != null)

            last = last.pre;

            if (last == null) first = null;

            else last.next = null;

       

   

 

 

    private Entry<K, V> getEntry(K key)

        return hashMap.get(key);

   

 

    @Override

    public String toString()

        StringBuilder sb = new StringBuilder();

        Entry entry = first;

        while (entry != null)

            sb.append(String.format("%s:%s", entry.key, entry.value));

            entry = entry.next;

       

        return sb.toString();

   

 

    class Entry<K, V>

        public Entry pre;

        public Entry next;

        public K key;

        public V value;

   

欢迎关注麦克叔叔每晚十点说,让我们一起交流与学习。

以上是关于LRU算法的主要内容,如果未能解决你的问题,请参考以下文章

LRU算法

LinkedHashMap:我还能实现LRU

什么是lru置换算法

缓存置换策略-LRU算法

算法LRU算法

吃透Redis:缓存淘汰篇-LRU算法