redis最近最少淘汰策略(LRU)模拟实现

一、背景介绍

        以前面试的时候,面试官问了一些redis相关的知识,比如说常用几种数据类型、穿透、击穿、雪崩、持久化方式、过期策略、淘汰策略等等,回答的都还凑合,画风一转,面试官突然问我,redis淘汰策略中,最近最少使用的这种策略底层的实现方式可以说一下吗?当时大脑一片空白,心中万马奔腾,不会......

二、lru算法介绍

        如果容器已满,有新元素到来时,删除最近最少使用的元素,给新元素腾出空间

三、实现方式

1、基于LinkedHashMap实现

/**
 * 基于java LinkedHashMap 实现lru算法
 * 继承LinkedHashMap
 * @author huhy
 */
public class LruDemoJavaApi<K,V> extends LinkedHashMap<K,V> {

    private int size;

    public LruDemoJavaApi(int size) {
        super(size, 0.75f,true);
        this.size = size;
}
    /**
     * 重写移除标准
     * 当目前容器实际容量大于初始设置的容量,触发淘汰最近最少使用元素策略
     * @param eldest
     * @return boolean
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return super.size()>size;
    }
    /**
     * main方法测试
     * @param args
     */
    public static void main(String[] args) {
        LruDemoJavaApi<Integer,Object> demoJavaApi = new LruDemoJavaApi<>(3);
        demoJavaApi.put(1,"1");
        demoJavaApi.put(2,2+"");
        demoJavaApi.put(3,3+"");
        System.out.println(demoJavaApi);
        demoJavaApi.put(4,4+"");
        System.out.println(demoJavaApi);
        demoJavaApi.get(2);
        System.out.println(demoJavaApi);
    }

2、双向链表实现

/**
 * node节点
 * node数据结构包含key、value,前后node节点引用
 * node是链表数据结构的关键组成
 * @param <K>
 * @param <V>
 */
class Node<K,V>{

    /** 前节点引用 */
    private Node prev;

    /** 后节点引用 */
    private Node next;

    /** 键 */
    private K key;

    /** 值 */
    private V value;

    public Node( K key, V value) {
        this.key = key;
        this.value = value;
        this.prev = this.next = null;
    }

    public Node() {
        this.prev = this.next = null;
    }

    public Node getPrev() {
        return prev;
    }

    public void setPrev(Node prev) {
        this.prev = prev;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return key+"="+value;
    }
}
/**
 * 双向链表数据结构
 * lru算法特点,最近最频繁使用的放在靠前位置,
 *  对于最近最频繁使用数据包括新增、修改、获取,所以新增元素,需要将新增节点放在队列最前
 * 1、包含头节点、尾节点
 * 2、初始状态,尾节点前节点指向头节点,头节点的后节点指向尾节点
 *
 */
class LinkedNode{

    private Node head;
    private Node tail;

    public LinkedNode() {
        this.head = new Node();
        this.tail = new Node();
        this.head.setNext(tail);
        this.tail.setPrev(head);
    }

    /**
     * 新增
     * 1、将头节点的后节点的前节点引用指向新增节点
     * 2、将新增节点的后节点指向头节点的厚街店
     * 3、将新增节点前节点指向头节点
     * 4、将头节点的后节点引用指向新增节点
     * @param node
     */
    public void addNode(Node node){
        if(Objects.nonNull(node)){
            this.head.getNext().setPrev(node);
            node.setNext(this.head.getNext());
            node.setPrev(this.head);
            this.head.setNext(node);
        }else { throw new RuntimeException("node is null");}
    }

    /**
     * 删除
     * 1、被删除节点的后节点的前节点引用指向被删除节点的前节点
     * 2、被删除节点的前节点的后节点引用指向被删除节点的后节点
     * 3、将被删除节点的前后节点置空,方便垃圾回收
     * @param node
     */
    public void removeNode(Node node){
        node.getNext().setPrev(node.getPrev());
        node.getPrev().setNext(node.getNext());
        node.setNext(null);
        node.setPrev(null);
    }

    /**
     * 获取尾节点,用于当队已满,淘汰最末节点时使用
     * @return
     */
    public Node getLastNode(){
        return tail.getPrev();
    }

    /**
     * 打印双向链表内所有元素
     * @return
     */
    @Override
    public String toString() {
        StringBuffer strBuff = new StringBuffer("{");
        Node node = head.getNext();
        strBuff.append(node.toString()+",");
        while (node.getNext()!=tail){
            node = node.getNext();
            strBuff.append(node.toString()+",");
        }
        String substring = strBuff.toString().substring(0, strBuff.length()-1);
        return substring+"}";
    }
}
/**
 * Lru算法模拟
 * 1、内部包含map、双向链表,新增、修改、删除需同时维护map和双向链表
 * 2、map中维护key<->node<k,v>数据结构
 * @author huhy
 */
public class LruDemoCustom {

    private Map<Integer,Node<Integer,Integer>> map = new HashMap<>();

    private LinkedNode linkedNode = new LinkedNode();

    private int size;

    public LruDemoCustom(int size) {
        this.size = size;
    }

    /**
     * put操作
     * 1、新增:
     *      1.1、将元素插入到map
     *      1.2 、将元素插入到双向链表
     *          1.2.1 、链表大小未超过初始设置的上限,正常插入
     *          1.2.2 、链表大小已超过初始设置的上线,则除插入
     *                  新元素外,需将队尾的元素进行删除操作
     * 2、修改:
     *      2.1、先从map中获取node元素,从双向链表中将该元素删除
     *      2.2、封装新的node元素插入到双向链表
     *      2.3、将新node元素设置到map中
     * @param key
     * @param value
     */
    public void put(int key, int value){
        Node node;
        if(!map.containsKey(key)){
            node = new Node(key,value);
            map.put(key,node);
            if(map.size()>this.size){
                Node lastNode = linkedNode.getLastNode();
                map.remove(lastNode.getKey());
                linkedNode.removeNode(lastNode);
            }
        }else{
            node = map.get(key);
            node.setValue(value);
            linkedNode.removeNode(node);
        }
        linkedNode.addNode(node);
    }

    /**
     * 获取
     * 1、从map中获取node元素
     * 2、将获取的node元素从双向链表中删除
     * 3、将获取的node元素新增到双向链表
     * @param key
     * @return
     */
    public int get(int key){
        if(!map.containsKey(key)){return -1;}
        Node<Integer, Integer> node = map.get(key);
        linkedNode.removeNode(node);
        linkedNode.addNode(node);
        return node.getValue();
    }

    @Override
    public String toString() {
        return linkedNode.toString();
    }
}
    /**
     * 测试
     * 观察是否符合lru算法标准
     * 1、新增、修改、查找 元素<k,v>是否总能在队头
     * 2、容量超过初始化所指定的大小,是否会淘汰掉最近最少使用的元素
     * @param args
     */
    public static void main(String[] args) {
        LruDemoCustom lruDemoCustom = new LruDemoCustom(3);
        lruDemoCustom.put(1,1);
        lruDemoCustom.put(2,2);
        lruDemoCustom.put(3,3);
        System.out.println(lruDemoCustom);
        lruDemoCustom.put(4,4);
        System.out.println(lruDemoCustom);
        lruDemoCustom.get(2);
        System.out.println(lruDemoCustom);
        lruDemoCustom.get(3);
        System.out.println(lruDemoCustom);
    }

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值