LRU缓存淘汰算法

一)LRU简介

LRU是Least Recently Used的缩写,即最近最少使用。是一种常用的页面置换算法,其所有操作都是在内存中进行。

设计原理:当数据在最近一段时间经常被访问,那么将来也可能被经常访问,这意味着,经常访问的数据需要能快速命中,而不常用的数据,需要淘汰掉。

缓存策略:先进先出策略 FIFO(First In,First Out)、最少使用策略 LFU(Least Frequently Used)、最近最少使用策略 LRU(Least Recently Used)。

 

LRU缓存淘汰算法图解

 

基本原理

第一步:新增数据直接插入到链表头部。

第二步:缓存数据被命中时,把缓存数据移动到链表头部。

第三步:当缓存容量已满时,移除链表尾部的数据,相当于淘汰最近最少使用的缓存。

 

实施方案

第一种:数组,但数组是查询速度快,增删慢。

第二种:单链表,但获取结点数据时,需要从头开始遍历查找结点,时间复杂度为O(n)。

第三种:双链表,查询速度慢,增删快,时间复杂度为O(1)。

第四种:HashMap+双链表,HashMap查询时间复杂度是O(1),可采用该方式。

 

二)LRU

第一步:初始化双链表数据结构,并初始化指定LRU缓存容量等。

/**
 * LRU缓存淘汰算法
 * 核心思想: 淘汰最近最少使用的数据
 * @author ouyangjun
 */
public class LRUCache {
	
    /** 双链表结构 */
    static class Node {
        Node prev; // 指向上一个结点
        Node next; // 指向下一个结点
        Object key;
        Object value;
		
        public Node(Object key, Object value, Node prev, Node next) {
            this.key = key;
            this.value = value;
            this.prev = prev;
            this.next = next;
        }
    }
	
    private Node first; // 头部结点
    private Node last; // 尾部结点
    private int size; // 双链表结点数量
    private int capacity; // 双链表容量
    private HashMap<Object, Node> cacheMap; // HashMap
	
    /** 初始化 */
    public LRUCache(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
        this.capacity = initialCapacity;
        cacheMap = new HashMap<Object, Node>(initialCapacity);
    }
}

 

第二步:增加几个辅助方法。查询LRU缓存数量,清空缓存,查询缓存是否存在。

/** LRU缓存数量 */
public int size() {
    return size;
}
	
/** 直接从Map中查找结点是否存在 */
public Object get(Object key) {
    Node node = cacheMap.get(key);
    if (node != null) {
        return node.value;
    }
    return null;
}
	
/** 清理LRU缓存 */
public void clear() {
    first = last = null;
    cacheMap.clear();
}

/** 打印LRU缓存数据 */
public void printLRUCache() {
    System.out.print("LRU缓存数据为: ");
    for(Node l = first; l != null; l = l.next) {
        System.out.print(l.key + ":" + l.value + " ");
    }
    System.out.println("\n");
}

 

第三步:移除尾部结点数据。当LRU缓存容量已满时,需移除。

/** 移除尾部结点 */
public Object removeLast() {
    Node l = last;
    if (l == null)
        return null;
    Node p = l.prev;
        
    Object value = l.value;
    l.value = null;
    l.prev = l; // help GC
        
    last = p;
    if (p == null)
        first = null;
    else
        p.next = null;
    --size;
    return value;
}

 

第四步:将缓存移动到链表头部。当缓存已存在并且被再次访问时,需移动到链表头部。

/** 将缓存移动到链表头部 */
private void moveToFirst(Node node) {
    if (first == node) { // 如果结点已经在头部, 就不需要移动
        return;
    }
    if (node == last) { // 判断结点是否在尾部
        last = last.prev;
    }
    if (node.next != null) { // 判断是否有后续结点
        node.next.prev = node.prev;
    }
    if (node.prev != null) { // 判断是否有前置结点
        node.prev.next = node.next;
    }
        
    if (first == null || last == null) { // 如果不存在结点, 直接作为头部尾部结点
        first = last = node;
        return;
    }
    node.next = first;
    first.prev = node;
    first = node; // 作为头部结点
    first.prev = null; // 头部结点的prev指向null
}

 

第五步:添加LRU缓存,流程如下。

/** 添加LRU缓存 */
public Object put(Object key, Object value) {
    if (key == null || value == null) {
        return null;
    }
		
    Node node = cacheMap.get(key);
    if (node == null) {
        if (size() >= capacity) {
            cacheMap.remove(key); // 移除Map中的一个对象
            removeLast(); // 移除链表尾部结点
        }
        // 结点不存在时, 新增结点, 缓存数量加1
        node = new Node(key, value, null, null);
        ++size;
    }
		
    // 把结点移动到头部
    moveToFirst(node);
		
    // 存储到Map中
    cacheMap.put(key, node);
    return value;
}

 

第六步:main方法测试

public static void main(String[] args) {
    LRUCache lru = new LRUCache(5);

    lru.put("A", "1");    // A:1
    lru.printLRUCache();
    lru.put("B", "2");    // B:2 A:1
    lru.printLRUCache();
    lru.put("C", "3");    // C:3 B:2 A:1
    lru.printLRUCache();
    lru.put("D", "4");    // D:4 C:3 B:2 A:1
    lru.printLRUCache();
    lru.put("E", "5");    // E:5 D:4 C:3 B:2 A:1
    lru.printLRUCache();
    lru.put("F", "6");    // F:6 E:5 D:4 C:3 B:2
    lru.printLRUCache();
    lru.put("C", "3");    // C:3 F:6 E:5 D:4 B:2
    lru.printLRUCache();
    lru.put("G", "7");    // G:7 C:3 F:6 E:5 D:4
    lru.printLRUCache();
}

测试打印效果图:

 

识别二维码关注个人微信公众号

本章完结,待续,欢迎转载!
 
本文说明:该文章属于原创,如需转载,请标明文章转载来源!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值