浅析建筑新材料在建设工程中的应用|浅析LRU(K-V)算法缓存教程

更新时间:2020-03-11    来源:php应用    手机版     字体:

【www.bbyears.com--php应用】

LRU(Least Recently Used)算法是缓存技术中的一种常见思想,顾名思义,最近最少使用,也就是说有两个维度来衡量,一个是时间(最近),一个频率(最少)。如果需要按优先级来对缓存中的K-V实体进行排序的话,需要考虑这两个维度,在LRU中,最近使用频率最高的排在前面,也可以简单的说最近访问的排在前面。这就是LRU的大体思想。

操作系统中,LRU是用来进行内存管理的页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。


wikipedia对LRU的描述:

n computing, cache algorithms (also frequently called cache replacement algorithms or cache replacement policies) are optimizinginstructions―or algorithms―that a computer program or a hardware-maintained structure can follow in order to manage a cache of information stored on the computer. When the cache is full, the algorithm must choose which items to discard to make room for the new ones.

Least Recently Used (LRU)

Discards the least recently used items first. This algorithm requires keeping track of what was used when, which is expensive if one wants to make sure the algorithm always discards the least recently used item. General implementations of this technique require keeping "age bits" for cache-lines and track the "Least Recently Used" cache-line based on age-bits. In such an implementation, every time a cache-line is used, the age of all other cache-lines changes. LRU is actually a family of caching algorithms with members including 2Q by Theodore Johnson and Dennis Shasha,[3] and LRU/K by Pat O'Neil, Betty O'Neil and Gerhard Weikum.[4]

LRUCache的分析实现

1.首先可以先实现一个FIFO的版本,但是这样只是以插入顺序来确定优先级的,没有考虑访问顺序,并没有完全实现LRUCache。

用Java中的LinkedHashMap实现非常简单。

private int capacity;
   
private java.util.LinkedHashMap cache = new java.util.LinkedHashMap() {
  @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
       return size() > capacity;
    }
};


程序中重写了removeEldestEntry()方法,如果大小超过了设置的容量就删除优先级最低的元素,在 FIFO版本中优先级最低的为最先插入的元素。


2.如果足够了解LinkedHashMap,实现LRUCache也是非常简单的。在LinkedHashMap中提供了可以设置容量、装载因子和顺序的构造方法。如果要实现LRUCache就可以把顺序的参数设置成true,代表访问顺序,而不是默认的FIFO的插入顺序。这里把装载因子设置为默认的0.75。并且还要重写removeEldestEntry()方法来维持当前的容量。这样一来可以有两种方法来实现LinkedHashMap版本的LRUCache。一种是继承一种是组合。

继承:

package lrucache.one;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 *LRU Cache的LinkedHashMap实现,继承。
 *@author wxisme
 *@time 2015-10-18 上午10:27:37
 */
public class LRUCache extends LinkedHashMap{
    
    private int initialCapacity;
    
    
    public LRUCache(int initialCapacity) {
        super(initialCapacity,0.75f,true);
        this.initialCapacity = initialCapacity;
    }
    
    @Override
    protected boolean removeEldestEntry(
            Map.Entry eldest) {
        return size() > initialCapacity;
    }
    @Override
    public String toString() {
        
        StringBuilder cacheStr = new StringBuilder();
        cacheStr.append("{");
        
        for (Map.Entry entry : this.entrySet()) {
            cacheStr.append("[" + entry.getKey() + "," + entry.getValue() + "]");
        }
        cacheStr.append("}");
        return cacheStr.toString();
    }
    
    
}


组合:

package lrucache.three;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 *LRU Cache 的LinkedHashMap实现,组合
 *@author wxisme
 *@time 2015-10-18 上午11:07:01
 */
public class LRUCache {
    private final int initialCapacity;
    
    private Map cache;
    
    public LRUCache(final int initialCapacity) {
        this.initialCapacity = initialCapacity;
        cache = new LinkedHashMap(initialCapacity, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(
                    Map.Entry eldest) {
                return size() > initialCapacity;
            }
        };
    }
    
    public void put(int key, int value) {
        cache.put(key, value);
    }
    
    public int get(int key) {
        return cache.get(key);
    }
    
    public void remove(int key) {
        cache.remove(key);
    }
    
    @Override
    public String toString() {
        
        StringBuilder cacheStr = new StringBuilder();
        cacheStr.append("{");
        
        for (Map.Entry entry : cache.entrySet()) {
            cacheStr.append("[" + entry.getKey() + "," + entry.getValue() + "]");
        }
        cacheStr.append("}");
        return cacheStr.toString();
    }
}



测试代码:


public static void main(String[] args) {
        
    LRUCache cache = new LRUCache(5);
    
    cache.put(5, 5);
    cache.put(4, 4);
    cache.put(3, 3);
    cache.put(2, 2);
    cache.put(1, 1);
    
    System.out.println(cache.toString());
    
    cache.put(0, 0);
    
    System.out.println(cache.toString());
    
}


运行结果:

{[5,5][4,4][3,3][2,2][1,1]}
{[4,4][3,3][2,2][1,1][0,0]}

可见已经实现了LRUCache的基本功能。

3.如果不用Java API提供的LinkedHashMap该如何实现LRU算法呢?首先我们要确定操作,LRU算法中的操作无非是插入、删除、查找并且要维护一定的顺序,这样我们有很多种选择,可以用数组,链表,栈,队列,Map中的一种或几种。先看栈和队列,虽然可以明确顺序实现FIFO或者FILO,但是LRU中是需要对两端操作的,既需要删除tail元素又需要移动head元素,可以想象效率是不理想的。我们要明确一个事实,数组和Map的只读操作复杂度为O(1),非只读操作的复杂度为O(n)。链式结构则相反。这么一来我们如果只使用其中的一种必定在只读或非只读操作上耗时过多。那我们大可以选择链表+Map组合结构。如果选择单向链表在对链表两端操作的时候还是要耗时O(n)。综上考虑,双向链表+Map结构应该是最好的。

在这种实现方式中,用双向链表来维护优先级顺序,也就是访问顺序。实现非只读操作。用Map存储K-V值,实现只读操作。访问顺序:最近访问(插入也是一种访问)的移动到链表头部,如果达到上限则删除链表尾部的元素。

package lrucache.tow;
import java.util.HashMap;
import java.util.Map;
/**
 *LRUCache链表+HashMap实现
 *@author wxisme
 *@time 2015-10-18 下午12:34:36
 */
public class LRUCache {
    
    private final int initialCapacity; //容量
    
    private Node head; //头结点
    private Node tail; //尾结点
    
    private Map> map;
    
    public LRUCache(int initialCapacity) {
        this.initialCapacity = initialCapacity;
        map = new HashMap>();
    }
    
    /**
     * 双向链表的节点
     * @author wxisme
     *
     * @param 
     * @param 
     */
    private class Node {
        public Node pre;
        public Node next;
        public K key;
        public V value;
        
        public Node(){}
        
        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
        
    }
    
    
    /**
     * 向缓存中添加一个K,V
     * @param key
     * @param value
     */
    public void put(K key, V value) {
        Node node = map.get(key);
        
        //node不在缓存中
        if(node == null) {
            //此时,缓存已满
            if(map.size() >= this.initialCapacity) {
                map.remove(tail.key); //在map中删除最久没有use的K,V
                removeTailNode();
            }
            node = new Node();
            node.key = key;
        }
        node.value = value;
        moveToHead(node);
        map.put(key, node);
    }
    
    /**
     * 从缓存中获取一个K,V
     * @param key
     * @return v
     */
    public V get(K key) {
        Node node = map.get(key);
        if(node == null) {
            return null;
        }
        //最近访问,移动到头部。
        moveToHead(node);
        return node.value;
    }
    
    /**
     * 从缓存中删除K,V
     * @param key
     */
    public void remove(K key) {
        Node node = map.get(key);
        
        map.remove(key); //从hashmap中删除
        
        //在双向链表中删除
        if(node != null) {
            if(node.pre != null) {
                node.pre.next = node.next;
            }
            if(node.next != null) {
                node.next.pre = node.pre;
            }
            if(node == head) {
                head = head.next;
            }
            if(node == tail) {
                tail = tail.pre;
            }
            
            //除去node的引用
            node.pre = null;
            node.next = null;
            node = null;
        }
        
    }
    
    
    /**
     * 把node移动到链表头部
     * @param node
     */
    private void moveToHead(Node node) {
        
        //切断node
        
        if(node == head) return ;
        
        if(node.pre !=null) {
            node.pre.next = node.next;
        }
        if(node.next != null) {
            node.next.pre = node.pre;
        }
        if(node == tail) {
            tail = tail.pre;
        }
        
        if(tail == null || head == null) {
            tail = head = node;
            return ;
        }
        
        
        //把node移送到head
        node.next = head;
        head.pre = node;
        head = node;
        node.pre = null;
        
    }
    
    /**
     * 删除链表的尾结点
     */
    private void removeTailNode() {
        if(tail != null) {
            tail = tail.pre;
            tail.next = null;
        }
    }
    
    
    @Override
    public String toString() {
        
        StringBuilder cacheStr = new StringBuilder();
        cacheStr.append("{");
        //因为元素的访问顺序是在链表里维护的,这里要遍历链表
        Node node = head;
        while(node != null) {
            cacheStr.append("[" + node.key + "," + node.value + "]");
            node = node.next;
        }
        
        cacheStr.append("}");
        
        return cacheStr.toString();
    }
}


测试数据:

public static void main(String[] args) {
            
    LRUCache cache = new LRUCache(5);
            
    cache.put(5, 5);
    cache.put(4, 4);
    cache.put(3, 3);
    cache.put(2, 2);
    cache.put(1, 1);
            
    System.out.println(cache.toString());
            
    cache.put(0, 0);
            
    System.out.println(cache.toString());
                
}



运行结果:

{[1,1][2,2][3,3][4,4][5,5]}
{[0,0][1,1][2,2][3,3][4,4]}

 也实现了LRUCache的基本操作。

等等!一样的测试数据为什么结果和上面LinkedHashMap实现不一样!

细心观察可能会发现,虽然都实现了LRU,但是双向链表+HashMap确实是访问顺序,而LinkedHashMap却还是一种插入顺序?

深入源码分析一下:


private static final long serialVersionUID = 3801124242820219131L;
    /**
     * The head of the doubly linked list.
     */
    private transient Entry header;
    /**
     * The iteration ordering method for this linked hash map: true
     * for access-order, false for insertion-order.
     *
     * @serial
     */
    private final boolean accessOrder;
 /**
     * LinkedHashMap entry.
     */
    private static class Entry extends HashMap.Entry {
        // These fields comprise the doubly linked list used for iteration.
        Entry before, after;
    Entry(int hash, K key, V value, HashMap.Entry next) {
            super(hash, key, value, next);
        }
private transient Entry header;
private static class Entry extends HashMap.Entry {
    Entry before, after;
    ……
}


从上面的代码片段可以看出,LinkedHashMap也是使用了双向链表,而且使用了Map中的Hash算法。LinkedHashMap是继承了HashMap,实现了Map的。


/**
     * Constructs an empty LinkedHashMap instance with the
     * specified initial capacity, load factor and ordering mode.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @param  accessOrder     the ordering mode - true for
     *         access-order, false for insertion-order
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public LinkedHashMap(int initialCapacity,
             float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }


上面的代码是我们使用的构造方法。

public V get(Object key) {
        Entry e = (Entry)getEntry(key);
        if (e == null)
            return null;
        e.recordAccess(this);
        return e.value;
    }
void recordAccess(HashMap m) {
            LinkedHashMap lm = (LinkedHashMap)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
    }
void recordRemoval(HashMap m) {
  remove();
}



这是实现访问顺序的关键代码。


/**
         * Inserts this entry before the specified existing entry in the list.
         */
        private void addBefore(Entry existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
void addEntry(int hash, K key, V value, int bucketIndex) {
        createEntry(hash, key, value, bucketIndex);
        // Remove eldest entry if instructed, else grow capacity if appropriate
        Entry eldest = header.after;
        if (removeEldestEntry(eldest)) {
            removeEntryForKey(eldest.key);
        } else {
            if (size >= threshold)
                resize(2 * table.length);
        }
    }
    /**
     * This override differs from addEntry in that it doesn't resize the
     * table or remove the eldest entry.
     */
    void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry old = table[bucketIndex];
    Entry e = new Entry(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header);
        size++;
    }


通过这两段代码我们可以知道,出现上面问题的原因是实现访问顺序的方式不一样,链表+HashMap是访问顺序优先级从前往后,而LinkedHashMap中是相反的。

拓展一下:

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    // Find a power of 2 >= initialCapacity
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;
    this.loadFactor = loadFactor;
    threshold = (int)(capacity * loadFactor);
    table = new Entry[capacity];
    init();
}


上面这段代码是HashMap的初始化代码,可以知道,初始容量是设置为1的,然后不断的加倍知道大于设置的容量为止。这是一种节省存储的做法。如果设置了装载因子,在后续的扩充操作中容量是初始设置容量和装载因子之积。

上面的所有实现都是单线程的。在并发的情况下不适用。可以使用java.util.concurrent包下的工具类和Collections工具类进行并发改造。

JDK中的LinkedHashMap实现效率还是很高的。可以看一个LeetCode的中的应用:http://www.cnblogs.com/wxisme/p/4888648.html



缓存淘汰算法之LRU


1. LRU


1.1. 原理

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。


1.2. 实现

最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:


1. 新数据插入到链表头部;

2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;

3. 当链表满的时候,将链表尾部的数据丢弃。


1.3. 分析

【命中率】

当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。

【复杂度】

实现简单。

【代价】

命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部。

 
2. LRU-K


2.1. 原理

LRU-K中的K代表最近使用的次数,因此LRU可以认为是LRU-1。LRU-K的主要目的是为了解决LRU算法“缓存污染”的问题,其核心思想是将“最近使用过1次”的判断标准扩展为“最近使用过K次”。


2.2. 实现

相比LRU,LRU-K需要多维护一个队列,用于记录所有缓存数据被访问的历史。只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。详细实现如下:


1. 数据第一次被访问,加入到访问历史列表;

2. 如果数据在访问历史列表里后没有达到K次访问,则按照一定规则(FIFO,LRU)淘汰;

3. 当访问历史队列中的数据访问次数达到K次后,将数据索引从历史队列删除,将数据移到缓存队列中,并缓存此数据,缓存队列重新按照时间排序;

4. 缓存数据队列中被再次访问后,重新排序;

5. 需要淘汰数据时,淘汰缓存队列中排在末尾的数据,即:淘汰“倒数第K次访问离现在最久”的数据。

LRU-K具有LRU的优点,同时能够避免LRU的缺点,实际应用中LRU-2是综合各种因素后最优的选择,LRU-3或者更大的K值命中率会高,但适应性差,需要大量的数据访问才能将历史访问记录清除掉。


2.3. 分析

【命中率】

LRU-K降低了“缓存污染”带来的问题,命中率比LRU要高。

【复杂度】

LRU-K队列是一个优先级队列,算法复杂度和代价比较高。

【代价】

由于LRU-K还需要记录那些被访问过、但还没有放入缓存的对象,因此内存消耗会比LRU要多;当数据量很大的时候,内存消耗会比较可观。

LRU-K需要基于时间进行排序(可以需要淘汰时再排序,也可以即时排序),CPU消耗比LRU要高。

3. Two queues(2Q)


3.1. 原理

Two queues(以下使用2Q代替)算法类似于LRU-2,不同点在于2Q将LRU-2算法中的访问历史队列(注意这不是缓存数据的)改为一个FIFO缓存队列,即:2Q算法有两个缓存队列,一个是FIFO队列,一个是LRU队列。


3.2. 实现

当数据第一次访问时,2Q算法将数据缓存在FIFO队列里面,当数据第二次被访问时,则将数据从FIFO队列移到LRU队列里面,两个队列各自按照自己的方法淘汰数据。详细实现如下:


1. 新访问的数据插入到FIFO队列;

2. 如果数据在FIFO队列中一直没有被再次访问,则最终按照FIFO规则淘汰;

3. 如果数据在FIFO队列中被再次访问,则将数据移到LRU队列头部;

4. 如果数据在LRU队列再次被访问,则将数据移到LRU队列头部;

5. LRU队列淘汰末尾的数据。

 

注:上图中FIFO队列比LRU队列短,但并不代表这是算法要求,实际应用中两者比例没有硬性规定。


3.3. 分析

【命中率】

2Q算法的命中率要高于LRU。

【复杂度】

需要两个队列,但两个队列本身都比较简单。

【代价】

FIFO和LRU的代价之和。

2Q算法和LRU-2算法命中率类似,内存消耗也比较接近,但对于最后缓存的数据来说,2Q会减少一次从原始存储读取数据或者计算数据的操作。

4. Multi Queue(MQ)


4.1. 原理

MQ算法根据访问频率将数据划分为多个队列,不同的队列具有不同的访问优先级,其核心思想是:优先缓存访问次数多的数据。


4.2. 实现

MQ算法将缓存划分为多个LRU队列,每个队列对应不同的访问优先级。访问优先级是根据访问次数计算出来的,例如

详细的算法结构图如下,Q0,Q1....Qk代表不同的优先级队列,Q-history代表从缓存中淘汰数据,但记录了数据的索引和引用次数的队列:


 

如上图,算法详细描述如下:

1. 新插入的数据放入Q0;

2. 每个队列按照LRU管理数据;

3. 当数据的访问次数达到一定次数,需要提升优先级时,将数据从当前队列删除,加入到高一级队列的头部;

4. 为了防止高优先级数据永远不被淘汰,当数据在指定的时间里访问没有被访问时,需要降低优先级,将数据从当前队列删除,加入到低一级的队列头部;

5. 需要淘汰数据时,从最低一级队列开始按照LRU淘汰;每个队列淘汰数据时,将数据从缓存中删除,将数据索引加入Q-history头部;

6. 如果数据在Q-history中被重新访问,则重新计算其优先级,移到目标队列的头部;

7. Q-history按照LRU淘汰数据的索引。


4.3. 分析

【命中率】

MQ降低了“缓存污染”带来的问题,命中率比LRU要高。

【复杂度】

MQ需要维护多个队列,且需要维护每个数据的访问时间,复杂度比LRU高。

【代价】

MQ需要记录每个数据的访问时间,需要定时扫描所有队列,代价比LRU要高。

注:虽然MQ的队列看起来数量比较多,但由于所有队列之和受限于缓存容量的大小,因此这里多个队列长度之和和一个LRU队列是一样的,因此队列扫描性能也相近。

 
5. LRU类算法对比

由于不同的访问模型导致命中率变化较大,此处对比仅基于理论定性分析,不做定量分析。

对比点     对比

命中率 LRU-2 > MQ(2) > 2Q > LRU

复杂度 LRU-2 > MQ(2) > 2Q > LRU

代价 LRU-2  > MQ(2) > 2Q > LRU

实际应用中需要根据业务的需求和对数据的访问情况进行选择,并不是命中率越高越好。例如:虽然LRU看起来命中率会低一些,且存在”缓存污染“的问题,但由于其简单和代价小,实际应用中反而应用更多。

 
java中最简单的LRU算法实现,就是利用jdk的LinkedHashMap,覆写其中的removeEldestEntry(Map.Entry)方法即可
如果你去看LinkedHashMap的源码可知,LRU算法是通过双向链表来实现,当某个位置被命中,通过调整链表的指向将该位置调整到头位置,新加入的内容直接放在链表头,如此一来,最近被命中的内容就向链表头移动,需要替换时,链表最后的位置就是最近最少使用的位置。
 

import java.util.ArrayList; 
import java.util.Collection; 
import java.util.LinkedHashMap; 
import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock; 
import java.util.Map; 
   
   
/**
 * 类说明:利用LinkedHashMap实现简单的缓存, 必须实现removeEldestEntry方法,具体参见JDK文档
 * 
 * @author dennis
 * 
 * @param 
 * @param 
 */ 
public class LRULinkedHashMap extends LinkedHashMap { 
    private final int maxCapacity; 
   
    private static final float DEFAULT_LOAD_FACTOR = 0.75f; 
   
    private final Lock lock = new ReentrantLock(); 
   
    public LRULinkedHashMap(int maxCapacity) { 
        super(maxCapacity, DEFAULT_LOAD_FACTOR, true); 
        this.maxCapacity = maxCapacity; 
    } 
   
    @Override 
    protected boolean removeEldestEntry(java.util.Map.Entry eldest) { 
        return size() > maxCapacity; 
    } 
    @Override 
    public boolean containsKey(Object key) { 
        try { 
            lock.lock(); 
            return super.containsKey(key); 
        } finally { 
            lock.unlock(); 
        } 
    } 
   
       
    @Override 
    public V get(Object key) { 
        try { 
            lock.lock(); 
            return super.get(key); 
        } finally { 
            lock.unlock(); 
        } 
    } 
   
    @Override 
    public V put(K key, V value) { 
        try { 
            lock.lock(); 
            return super.put(key, value); 
        } finally { 
            lock.unlock(); 
        } 
    } 
   
    public int size() { 
        try { 
            lock.lock(); 
            return super.size(); 
        } finally { 
            lock.unlock(); 
        } 
    } 
   
    public void clear() { 
        try { 
            lock.lock(); 
            super.clear(); 
        } finally { 
            lock.unlock(); 
        } 
    } 
   
    public Collection> getAll() { 
        try { 
            lock.lock(); 
            return new ArrayList>(super.entrySet()); 
        } finally { 
            lock.unlock(); 
        } 
    } 
}

    

 基于双链表 的LRU实现:

  传统意义的LRU算法是为每一个Cache对象设置一个计数器,每次Cache命中则给计数器+1,而Cache用完,需要淘汰旧内容,放置新内容时,就查看所有的计数器,并将最少使用的内容替换掉。

   它的弊端很明显,如果Cache的数量少,问题不会很大, 但是如果Cache的空间过大,达到10W或者100W以上,一旦需要淘汰,则需要遍历所有计算器,其性能与资源消耗是巨大的。效率也就非常的慢了。

    它的原理: 将Cache的所有位置都用双连表连接起来,当一个位置被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。

     这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾则表示最近最少使用的Cache。

     当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘汰链表最后的部分即可。

  上面说了这么多的理论, 下面用代码来实现一个LRU策略的缓存。

    我们用一个对象来表示Cache,并实现双链表,

public class LRUCache {
    /**
     * 链表节点
     * @author Administrator
     *
     */
    class CacheNode {
        ……
    }
    private int cacheSize;//缓存大小
    private Hashtable nodes;//缓存容器
    private int currentSize;//当前缓存对象数量
    private CacheNode first;//(实现双链表)链表头
    private CacheNode last;//(实现双链表)链表尾
}

                  

 下面给出完整的实现,这个类也被Tomcat所使用( org.apache.tomcat.util.collections.LRUCache),但是在tomcat6.x版本中,已经被弃用,使用另外其他的缓存类来替代它。

public class LRUCache {
    /**
     * 链表节点
     * @author Administrator
     *
     */
    class CacheNode {
        CacheNode prev;//前一节点
        CacheNode next;//后一节点
        Object value;//值
        Object key;//键
        CacheNode() {
        }
    }
    public LRUCache(int i) {
        currentSize = 0;
        cacheSize = i;
        nodes = new Hashtable(i);//缓存容器
    }
    
    /**
     * 获取缓存中对象
     * @param key
     * @return
     */
    public Object get(Object key) {
        CacheNode node = (CacheNode) nodes.get(key);
        if (node != null) {
            moveToHead(node);
            return node.value;
        } else {
            return null;
        }
    }
    
    /**
     * 添加缓存
     * @param key
     * @param value
     */
    public void put(Object key, Object value) {
        CacheNode node = (CacheNode) nodes.get(key);
        
        if (node == null) {
            //缓存容器是否已经超过大小.
            if (currentSize >= cacheSize) {
                if (last != null)//将最少使用的删除
                    nodes.remove(last.key);
                removeLast();
            } else {
                currentSize++;
            }
            
            node = new CacheNode();
        }
        node.value = value;
        node.key = key;
        //将最新使用的节点放到链表头,表示最新使用的.
        moveToHead(node);
        nodes.put(key, node);
    }
    /**
     * 将缓存删除
     * @param key
     * @return
     */
    public Object remove(Object key) {
        CacheNode node = (CacheNode) 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;
        }
        return node;
    }
    public void clear() {
        first = null;
        last = null;
    }
    /**
     * 删除链表尾部节点
     *  表示 删除最少使用的缓存对象
     */
    private void removeLast() {
        //链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)
        if (last != null) {
            if (last.prev != null)
                last.prev.next = null;
            else
                first = null;
            last = last.prev;
        }
    }
    
    /**
     * 移动到链表头,表示这个节点是最新使用过的
     * @param node
     */
    private void moveToHead(CacheNode 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;
    }
    private int cacheSize;
    private Hashtable nodes;//缓存容器
    private int currentSize;
    private CacheNode first;//链表头
    private CacheNode last;//链表尾
}


本文来源:http://www.bbyears.com/jiaocheng/86484.html

热门标签

更多>>

本类排行