算法学习笔记(10)-缓存淘汰算法LRU

本文深入解析了LRU(最近最少使用)算法,一种常见的缓存淘汰策略。文章详细介绍了LRU算法的定义、实现方式,包括使用双向链表和HashMap进行实现的过程,并解释了为何不直接使用HashMap作为LRU的底层结构。此外,还提供了一种基于LinkedHashMap的简化实现方案。

今日心情美丽,让咱们看看传说中的LRU算法是怎么一回事

1.1 何谓LRU算法

LRU算法指的是Least Recently Used,即最近最少使用算法,也被称作缓存淘汰算法/页面置换算法,LRU算法的基本原则是如果一个数据最近很少被访问到,那么之后也应该很少被访问到;如果一个数据最近经常被访问到,那么置换也应该经常被访问到.

1.2 如何实现

实现LRU算法一般有以下几种做法:

  • 双向链表实现
    通过双向链表链表可以实现一个LRU缓存结果,做法是

    1.新添加的数据放置在链头
    2.将被访问到的数据移动到链头
    3.当链表满载时,将链尾数据丢弃

    如图,通过这三步就可以将经常被访问到的数据放置在链头,从而在下次访问时减少从头至尾的对比时间;而不经常被访问的数据则放置在链尾,当链表满载时,将其删除:

[外链图片转存失败(img-zmQ9Eoz8-1567614666654)(picture/LRU.png)]

代码实现

/**
 * @Auther: ARong
 * @Date: 19-9-5 上午00:26
 * @Description: 基于双向链表和HashMap的LRU算法实现
 **/
public class LRUCache<K, V> {
    //链头
    private Node head;
    //链尾
    private Node end;
    //链表所容纳的数据量16
    private  int capacity;
    //存储Node的HashMap
    HashMap<K, Node> map = new HashMap<>();

    //构造方法中指定容量
    public LRUCache(int capacity) {
        this.capacity = capacity;
    }
    public LRUCache() {
        this.capacity = 16;
    }

    //双向链表节点
    class Node {
        Node pre;
        Node next;
        K key;
        V value;

        Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    /**
     * @auther: Arong
     * @description: 添加缓存数据
     * @param: key value
     * @return: void
     * @date: 19-9-5 上午00:26
     */
    public void add(K key, V value) {
        //检查是否需要删除尾部元素
        check();
        //分情况在链头添加数据
        if (map.containsKey(key)) {
            //map中含有该缓存,更新其value,并将其置于链表首部
            Node node = map.get(key);
            node.value = value;
            removeNode(node);
            putHead(node);
        } else {
            //map中不含有该数据,直接添加到map中和链表头部即可
            Node node = new Node(key, value);
            map.put(key, node);
            putHead(node);
        }
    }
    /**
     * @auther: Arong
     * @description: 将Node置于链表头部
     * @param: [node]
     * @return: void
     * @date: 19-9-5 上午00:26
     */
    private void putHead(Node node) {
        if (node == end) {
            end = node.pre;
        }
        if (end == null) {
            end = node;
        }
        node.pre = null;
        node.next = head;
        if (head != null) {
            head.pre = node;
        }
        head = node;
    }

    /**
     * @auther: Arong
     * @description: 检查是否需要删除尾部数据
     * @param: []
     * @return: void
     * @date: 19-9-5 上午00:26
     */
    private void check() {
        if (map.size() == capacity) {
            //需要删除尾部元素
            removeNode(end);
        }
    }



    /**
     * @auther: Arong
     * @description: 获取缓存数据
     * @param: [key]
     * @return: V
     * @date: 19-9-5 上午00:26
     */
    public V get(K key) {
        //在链表中搜索该数据
        if (map.containsKey(key)) {
            //将其V返回.并且将Node置于链头
            Node node = map.get(key);
            removeNode(node);
            putHead(node);
            return node.value;
        } else {
            return null;
        }
    }

    /**
     * @auther: Arong
     * =@description: 删除缓存
     * @param: [key]
     * @return: V
     * @date: 19-9-5 上午00:26
     */
    public V remove(K key) {
        if (map.containsKey(key)) {
            Node node = map.get(key);
            map.remove(key);
            removeNode(node);
            return node.value;
        } else {
            return null;
        }
    }

    /**
     * @auther: Arong
     * @description: 删除节点
     * @param: [node]
     * @return: void
     * @date: 19-9-5 上午00:26
     */
    private void removeNode(Node node) {
        if (node.pre != null) {
            node.pre.next = node.next;
        } else {
            head = node.next;
            head.pre = null;
        }

        if (node.next != null) {
            node.next.pre = node.pre;
        } else {
            end = node.pre;
        }
    }

}

1.3 为何不直接使用HashMap作为LRU底层结构

1.若不限定HashMap的容量,那么至多可以存储2^30个缓存数据,但是多于这个就无法再存入数据,并且缓存数据过多,内存占用巨大会产生OOM问题; 2.若限定HashMap的容量,例如限定为16,使用put时还可以直接放入HashMap中,但若数量超过了16,由于无法记录哪个数据被访问到得最少,那么就无法删去数据,也就无法做到动态地维持容量平衡;

1.4 基于LinkedHashMap实现LRUCache

下面介绍一种实现LRU的更简单的方式,它基于java.util.LinkedHashMap,LinkedHashMap底层采用的就是上文中链表+HashMap的实现,原理和上文一致:

public class LRUCacheByLinkedHashMap<K, V> extends LinkedHashMap<K, V> {

    private int capacity;

    public LRUCacheByLinkedHashMap(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    /**
     * @auther: Arong
     * @description: 重写删除最后访问元素的方法
     * @param: [eldest]
     * @return: boolean
     * @date: 下午6:52 19-9-5
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        //LinkedList在每次get时会调用该方法去判断需不需要删除最久没有被访问到的元素,返回true则删除
        return super.size() > capacity;
    }

    /**
     * @auther: Arong
     * @description: 添加元素
     * @param: [key, value]
     * @return: void
     * @date: 下午6:49 19-9-5
     */
    public void add(K key, V value) {
        super.put(key, value);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BoringRong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值