LRU算法的java实现

LRU全称是Least Recently Used,即最近最久未使用的意思。LRU算法的设计原则是:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。是缓存中一种常见的机制。下图展示了逻辑页面缓存的访问情况
这里写图片描述

public class LRUPageFrame {

    private static class Node { 
        Node prev;
        Node next;
        int pageNum;

        Node() {
        }
    }

    private int capacity;
    private int currentSize;
    private Node first;// 链表头
    private Node last;// 链表尾

    public LRUPageFrame(int capacity) {
        this.currentSize = 0;
        this.capacity = capacity;
    }

    /**
     * 获取缓存中对象
     * 
     * @param key
     * @return
     */
    public void access(int pageNum) {

        Node node = find(pageNum);
        //在该队列中存在, 则提到队列头
        if (node != null) {
       moveExistingNodeToHead(node);          
        } else{
            node = new Node();
            node.pageNum = pageNum;
            // 缓存容器是否已经超过大小.
            if (currentSize >= capacity) {            
                removeLast();                
            }  
            addNewNodetoHead(node);                                                    
        }
    }

    private void addNewNodetoHead(Node node) {
        if(isEmpty()){
            node.prev = null;
            node.next = null;
            first = node;
            last = node;
        } else{
            node.prev = null;
            node.next = first;
            first.prev = node;
            first = node;
        }
        this.currentSize ++;
    }

    private Node find(int data){
        Node node = first;
        while(node != null){
            if(node.pageNum == data){
                return node;
            }
            node = node.next;
        }
        return null;

    }

    /**
     * 删除链表尾部节点 表示 删除最少使用的缓存对象
     */
    private void removeLast() {
        Node prev = last.prev;
        prev.next = null;
        last.prev = null;
        last = prev;
        this.currentSize --;
    }

    /**
     * 移动到链表头,表示这个节点是最新使用过的
     * 
     * @param node
     */
    private void moveExistingNodeToHead(Node node) {
        if (node == first) {
            return;
        }
        else if(node == last){
            //当前节点是链表尾, 需要放到链表头
            Node prevNode = node.prev;
            prevNode.next = null;    
            last.prev = null;
            last  = prevNode;                
        } else{
            //node 在链表的中间, 把node 的前后节点连接起来
            Node prevNode = node.prev;
            prevNode.next = node.next;
            Node nextNode = node.next;
            nextNode.prev = prevNode;  
        }   
        node.prev = null;
        node.next = first;
        first.prev = node;
        first = node;    

    }
    private boolean isEmpty(){        
        return (first == null) && (last == null);
    }
}



  •   最简单的一种方法是利用LinkHashMap,因为它本身就有一个方法就是在所设置的缓存范围内,去除掉额外的旧数据
复制代码
 1 public class LRUByHashMap<K, V> {
 2  /*
 3   * 通过LinkHashMap简单实现LRU算法
 4   */
 5  /**
 6   * 缓存大小
 7   */
 8  private int cacheSize;
 9  /**
10   * 当前缓存数目
11   */
12  private int currentSize;
13  
14  private LinkedHashMap<K, V> maps;
15  
16  public LRUByHashMap(int cacheSize1) {
17   this.cacheSize = cacheSize1;
18  
19   maps = new LinkedHashMap<K, V>() {
20    /**
21     *
22     */
23    private static final long serialVersionUID = 1;
24  
25    // 这里移除旧的缓存数据
26    @Override
27    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
28  
29     // 当超过缓存数量的时候就将旧的数据移除
30     return getCurrentSize() > LRUByHashMap.this.cacheSize;
31    }
32   };
33  }
34  
35  public synchronized int getCurrentSize() {
36   return maps.size();
37  }
38  
39  public synchronized void put(K k, V v) {
40  
41   if (k == null) {
42    throw new Error("存入的键值不能为空");
43   }
44  
45   maps.put(k, v);
46  }
47  
48  public synchronized void remove(K k) {
49   if (k == null) {
50    throw new Error("移除的键值不能为空");
51   }
52   maps.remove(k);
53  }
54  
55  public synchronized void clear() {
56   maps = null;
57  }
58  
59  // 获取集合
60  public synchronized Collection<V> getCollection() {
61  
62   if (maps != null) {
63    return maps.values();
64   } else {
65    return null;
66   }
67  
68  }
69  
70  public static void main(String[] args) {
71  
72   // 测试
73   LRUByHashMap<Integer, String> maps = new LRUByHashMap<Integer, String>(
74     3);
75   maps.put(1, "1");
76   maps.put(2, "2");
77   maps.put(3, "3");
78   maps.put(4, "4");
79   maps.put(5, "5");
80   maps.put(6, "6");
81   Collection<String> col = maps.getCollection();
82   System.out.println("存入缓存中的数据是--->>" + col.toString());
83  
84  }
85 }
复制代码
运行后的效果:
 
   
代码明明是put了6个Entry但最后只显示了三个,之间的三个是旧的所以直接被咔嚓掉了
  • 第二种方法是利用双向链表 + HashTable
    双向链表的作用是用来记录位置的,而HashTable作为容器来存储数据的
    为什么用HashTable不用HashMap呢?
  1.  HashTable的键和值都不能为null;
  2. 上面用LinkHashMap实现的LRU,有用到 synchronized , 让线程同步去处理,这样就避免在多线程处理统一数据时造成问题
而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   node.key = key
 48   moveToHead(node);
 49   nodes.put(key, node);
 50  }
 51  
 52  /**
 53   * 将entry删除, 注意:删除操作只有在cache满了才会被执行
 54   */
 55  public void remove(Object key) {
 56   Entry node = nodes.get(key);
 57   //在链表中删除
 58   if (node != null) {
 59    if (node.prev != null) {
 60     node.prev.next = node.next;
 61    }
 62    if (node.next != null) {
 63     node.next.prev = node.prev;
 64    }
 65    if (last == node)
 66     last = node.prev;
 67    if (first == node)
 68     first = node.next;
 69   }
 70   //在hashtable中删除
 71   nodes.remove(key);
 72  }
 73  
 74  /**
 75   * 删除链表尾部节点,即使用最后 使用的entry
 76   */
 77  private void removeLast() {
 78   //链表尾不为空,则将链表尾指向null. 删除连表尾(删除最少使用的缓存对象)
 79   if (last != null) {
 80    if (last.prev != null)
 81     last.prev.next = null;
 82    else
 83     first = null;
 84    last = last.prev;
 85   }
 86  }
 87  
 88  /**
 89   * 移动到链表头,表示这个节点是最新使用过的
 90   */
 91  private void moveToHead(Entry node) {
 92   if (node == first)
 93    return;
 94   if (node.prev != null)
 95    node.prev.next = node.next;
 96   if (node.next != null)
 97    node.next.prev = node.prev;
 98   if (last == node)
 99    last = node.prev;
100   if (first != null) {
101    node.next = first;
102    first.prev = node;
103   }
104   first = node;
105   node.prev = null;
106   if (last == null)
107    last = first;
108  }
109  /*
110   * 清空缓存
111   */
112  public void clear() {
113   first = null;
114   last = null;
115   currentSize = 0;
116  }
117  
118 }
119  
120 class Entry {
121  Entry prev;//前一节点
122  Entry next;//后一节点
123  Object value;//
124  Object key;//
125 }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值