记录一下基于hashmap + 双向链表实现LRU 算法

 流程图:

代码如下: 

package com.feifei.demo.arithmetic;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;

import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 基于hashmap + 双向链表实现LRU
 * <p>
 * hashMap用来检索定位到Node,Node用于删除和更新
 * hashMap中的key
 * <p>
 * LRU least recent used 最近最少使用
 * 一般用于缓存中,比如缓存中间件redis
 *
 * <1> 队列不行吗?
 * 不行队列只能做到先进先出,但是重复用到中间的数据时无法把中间的数据移动到顶端。
 * <2> 单链表不行吗?
 * 单链表能实现新来的放头部,最久不用的在尾部删除。但删除的时候需要遍历到尾部,因为单链表只有头指针。
 * 在用到已经用到过的数据时,还要遍历整合链表,来确定是否用过,然后再遍历到响应位置来剔除的节点,并重新放在头部。这效率可想而知。
 * <p>
 * 这时hashmap的作用就出来了 他可以在单位1的时间判断value的值是否存在,key直接存储节点对象,
 * 能直接定位删除对应的节点(将比节点的父节点指向子节点)。要通过一个节点直接获得父节点的话,通过单链表是不行的。
 * 这时双向链表的作用也提现出来了。能直接定位到父节点。
 * 这效率就很高了。而且由于双向链表有尾指针,所以剔除最后的尾节点也十分方便,快捷。
 * <p>
 * redis中的过期策略采用的是定时过期 + 懒惰过期
 * redis中的淘汰策略有好几种,其中主要使用的是LRU
 *
 * @author xiaofeifei
 * @date 2020-03-15
 * @since
 */
@FieldDefaults(level = AccessLevel.PRIVATE)
public class LRUTest<K, V> {

    /**
     * 设置最大容量,如果达到了则进行LRU
     */
    int capacity;

    /**
     * 检索表,K记录Node的key, V记录Node的value
     */
    ConcurrentHashMap<K, Node<K, V>> hashMap;

    /**
     * 头结点
     */
    Node<K, V> head;

    /**
     * 尾结点
     */
    Node<K, V> tail;

    public LRUTest(int capacity) {
        // 初始化hashMap
        this.capacity = capacity;
        hashMap = new ConcurrentHashMap<>(capacity);
        head = new Node<>();
        tail = new Node<>();
        head.prev = null;
        head.next = tail;
        tail.prev = head;
        tail.next = null;
    }


    @Setter
    @Getter
    @FieldDefaults(level = AccessLevel.PRIVATE)
    private static class Node<K, V> {

        K key;

        V data;

        Node<K, V> prev;

        Node<K, V> next;
    }

    /**
     * 添加一个元素
     * 添加时校验容量是否达到阈值,达到了则进行LRU
     * 校验是否重复,重复则直接覆盖
     */
    public void add(K key, V data) {
        // 已存在则直接覆盖
        if (hashMap.contains(key)) {
            Node<K, V> node = hashMap.get(key);
            node.setKey(key);
            node.setData(data);
            return;
        }

        // 不存在则添加
        // 校验是否达到阈值,达到则移除链尾node以及移除hash中的元素
        if (hashMap.size() >= capacity) {
            // 移除hash中的链尾数据
            hashMap.remove(tail.prev.getKey());
            // 移除链尾的node
            tail.prev = tail.prev.prev;
            tail.prev.next = tail;
        }
        Node<K, V> node = new Node<>();
        node.setData(data);
        node.setKey(key);
        // 现在的头部node指向原来的头部node
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
        node.prev = head;
        // 同步hash
        hashMap.put(key, node);
    }

    /**
     * 获取一个元素
     * 查询时校验key是否存在,
     * 存在则将当前Node转换到链首
     */
    public Node<K, V> get(K key) {
        Node<K, V> node = hashMap.get(key);
        // 校验key是否存在,不存在则返回为null
        if (Objects.isNull(node)) {
            return null;
        }
        // 设置到链首
        if (head.next == node) {
            return node;
        }

        // 上一个节点的下一个指向下一个节点的上一个 node.prev.next -> node ===> node.prev.next -> node.next
        node.prev.next = node.next;
        // 下一个节点的上一个指向上一个节点的下一个 node.next.prev -> node ===> node.next.prev -> node.prev
        node.next.prev = node.prev;

        // 移动当前节点至链首
        // head的下一个节点的上一个执行当前节点 head.next.prev -> head ===> head.next.prev -> node
        head.next.prev = node;
        // 当前节点的下一个指向head节点的下一个 node.next -> node.next ===> node.next -> head.next
        node.next = head.next;
        // head.next -> head.next ===> head.next -> node
        head.next = node;
        // node.prev -> node.prev ===> node.prev -> head
        node.prev = head;

        return node;
    }

    public static void main(String[] args) {
        LRUTest<String, String> test = new LRUTest<>(5);
        test.add("bibi", "bibi");
        test.add("xixi", "xixi");
        test.add("haha", "haha");
        test.add("lala", "lala");
        test.add("bobo", "bobo");
        test.add("biubiu", "biubiu");
        test.add("zizi", "zizi");

        System.out.println("bibi is null ? " + Objects.isNull(test.get("bibi")));
        System.out.println("xixi is null ? " + Objects.isNull(test.get("xixi")));
        System.out.println("haha is null ? " + Objects.isNull(test.get("haha")));
        Node<String, String> haha = test.get("haha");
        System.out.println("haha key: " + haha.getKey() + ", haha data: " + haha.getData());
    }


}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值