LRU算法

本文 利用双链表+hashMap实现LRU算法 

为什么要选双链表+HashMap?

因为,LRU的核心是“最近最少使用”,主要的功能有:

1. 在get数据时,先获取到数据,然后会将该数据放到链表最后位置,方便下次取。(get时可以先到链表尾部取,看是不是)

2. 在put数据时,首先看链表中有没有,有就将这个数据放到链表尾部,方便下次取,没有就在链表尾部插入。

3. 在put数据时,看存入数量满了没,满了就删除链表头部的元素(即删除最老元素)

因此:

使用HashMap的原因:都有查询数据的操作,使用HashMap可以快速查到有没有数据,

使用双向链表的原因:有插入与删除的操作,选用Linked双向链表更有优势。

 

代码:

package Algorithm.LRU;

import java.util.HashMap;
import java.util.Map;

/**
 * LRU(Least Recently Used)即“最近最少使用”,这里使用双链表+hashMap实现了LRU算法
 * 注意:LRUCache中没有重复的节点
 *
 * @author chenjunjie
 * @since 2018-04-25
 */
public class LRUCache {
    Node head, tail;
    int size;
    int capacity;
    Map<Integer,Node> map;

    /**
     * 定义内部类,Node节点
     */
    static class Node {
        int key;
        int val;
        Node pre;
        Node post;

        /**
         * 构造函数
         * @param key Map的key
         * @param val May的value
         */
        Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }

    /**
     * 构造函数,初始化
     * @param capacity
     */
    public LRUCache(int capacity) {
        this.head = null;
        this.tail = null;
        this.size = 0;
        this.capacity = capacity;
        // 使用了HashMap
        // 注意这里Map中的value为Node类型,这样可以方便索引到某个具体node
        this.map = new HashMap<Integer, Node>();
    }

    /**
     * 获取最近使用node
     * 先remove掉节点以前所处位置,然后添加到链表的尾部,从而达到最近使用的效果
     * @param key
     * @return 返回value
     */
    public int get(int key) {
        if(!map.containsKey(key)) {
            return -1;
        }
        Node temp = map.get(key);
        remove(temp);
        add(temp);
        return temp.val;
    }

    /**
     * 添加新节点,并保证放入最后节点
     * @param key map的key值
     * @param value map中的value
     */
    public void put(int key, int value) {
        Node temp;
        // 如果之前有key,但不是放在链表尾部,则先删除然后添加到尾部
        if(map.containsKey(key)){
            temp = map.get(key);
            remove(temp);
            map.remove(key);
            size--;
        }

        // 如果超过了容量,这删除头部节点,即删除“最老”节点
        if(size == capacity){
            Node h = head;
            remove(h);
            map.remove(h.key);
            size--;
        }

        temp = new Node(key,value);
        // 链表中添加Node
        add(temp);
        // HashMap添加Entry
        map.put(key,temp);
        size++;
    }

    /**
     * 普通的linked链表删除操作
     * @param n 要删除的节点
     */
    private void remove(Node n) {
        // 如果只有一个节点
        if(n==head && n==tail){
            head = null;
            tail = null;
        }
        // 如果是头节点
        else if(n == head) {
            head = head.post;
            head.pre = null;
        }
        // 如果是尾节点
        else if(n == tail) {
            tail = tail.pre;
            tail.post = null;
        }
        //
        else{
            n.pre.post = n.post;
            n.post.pre = n.pre;
        }
        n.pre = null;
        n.post = null;
    }

    /**
     * 在链表尾部加入节点
     * @param n 添加节点
     */
    private void add(Node n) {
        if(head == null && tail == null) {
            head = n;
            tail = n;
        }else {
            tail.post = n;
            n.pre = tail;
            tail = n;
        }
    }

    /**
     * getMap,方便测试遍历查询结果
     * @return
     */
    public Map<Integer, Node> getMap() {
        return map;
    }

    /**
     * getHead,方便测试遍历查询结果,实际上不要暴露出来
     * @return
     */
    public Node getHead(){
        return head;
    }


}

测试:

public static void main (String[] args) {
    LRUCache lurCache = new LRUCache(10);
    int N = 12 ; //7
    int i = 1;
    while (i<N){
        lurCache.put(i,i*2);
        i++;
    }

    System.out.println(lurCache.get(4));
    lurCache.put(4,666);

    // 遍历map
    Map hsMap = lurCache.getMap();
    Iterator it = hsMap.entrySet().iterator();
    while(it.hasNext()){
        Map.Entry<Integer,LRUCache.Node> result = (Map.Entry<Integer,LRUCache.Node>)it.next();
        System.out.printf("(key=%d,value=%d)\n",result.getKey(),result.getValue().val);
    }

    System.out.println("-----------");

    // 遍历node
    LRUCache.Node n = lurCache.getHead();
    LRUCache.Node cur = n;
    while(cur != null){
        System.out.printf("(key=%d,value=%d)\n",cur.key,cur.val);
        cur = cur.post;
    }
}

结果如下:

 

特性:

1. 插入了12条数据,由于定义的容量为10,因此每次满容量时删除头结点的数据。

2. 修改key=4的值,修改后放到队列尾部

3. 使用get()后,node节点的数据也会放到队列尾部(ps:测试代码中没有写)

 

扩展(待续):

/**
* LRU是Least Recently Used 的缩写,即“最近最少使用”
* 扩展 LinkedHashMap 可以实现就实现了LRU,
* LinkedHashMap中最近读取的会放在最前面,最最不常读取的会放在最后,同时
* 调用removeEldestEntry会移除链表中“最老”的节点,但注意源码中调用该方法返回的值为false
* 因此,可以继承LinkedHashMap重写removeEldestEntry, 从而构建LRU缓存
*/

 

posted @ 2018-04-25 15:50  有爱jj  阅读(380)  评论(0编辑  收藏  举报