定义

LRU(Least Recently Used)即最少使用,是一种内存管理算法。LRU算法基于一种假设:长期不被使用的数据,在未来被使用的几率也不大。因此当数据量达到一定阈值时,则需要删除最近最少被使用的数据。

LRU实现的思路

使用HashMap+双向链表的方式。

image.png

当访问Key3时,会将Value3在链表中的位置更新至表尾

image.png

如果容量满了,此时插入数据会将表头的键值对删除,再将新的节点添加至表尾以及HashMap中

image.png

代码实现

其实JDK已经为我们封装好了HashMap+双向链表的数据结构,即LinkedHashMap,当然我们也可以自己去实现。

接口定义

public interface LRUCache {

    Object getKey(String key);

    void putKey(String key, Object value);

}

LinkedHashMap实现

public class LinkedHashMapLRUCache implements LRUCache {

    private LinkedHashMap<String, Object> linkedHashMap;
    /**
     * 容量
     */
    private final int cacheSize;

    public LinkedHashMapLRUCache(int capacity) {
        this.cacheSize = capacity;
        // 三个构造函数的意义:
        // 第一个参数表示容量
        // 第二个参数表示负载因子
        // 第三个为linkedHashMap的排序模式.true代表根据最近访问顺序排序,false表示根据插入顺序排序.
        linkedHashMap = new LinkedHashMap<String, Object>(capacity, 0.75f, true) {
            /**
             * 钩子方法,通过put新增键值对的时候,若该方法返回true
             * 便移除该map中最老的键和值
             */
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。
                return size() > cacheSize;
            }
        };
    }

    @Override
    public Object getKey(String key) {
        return linkedHashMap.get(key);
    }

    @Override
    public void putKey(String key, Object value) {
        System.out.println("===> putKey:" + key + ",value:" + value);
        linkedHashMap.put(key, value);
    }
}


自定义HashMap+LinkedList实现

public class CustomLRUCache implements LRUCache {

    private HashMap<String, Node> hashMap;
    // 头节点
    private Node head;
    // 尾节点
    private Node tail;

    /**
     * 容量
     */
    private final int cacheSize;

    public CustomLRUCache(int cacheSize) {
        hashMap = new HashMap<>(cacheSize);
        this.cacheSize = cacheSize;
    }

    /**
     * 双向链表的节点
     */
    public class Node {
        // 前驱节点
        private Node prev;
        // 后驱节点
        private Node next;
        // key值
        private String key;
        // value值
        private Object value;

        public Node(String key, Object value) {
            this.key = key;
            this.value = value;
        }

    }


    @Override
    public Object getKey(String key) {
        Node node = hashMap.get(key);

        //如果为空则返回空
        if (node == null) {
            return null;
        }

        //如果不为空则更新Node位置
        refreshNode(node);
        // 返回数据
        return node.value;

    }

    @Override
    public void putKey(String key, Object value) {
        Node node = hashMap.get(key);

        if (node == null) {
            // node 不存在,则需要插入数据

            // 如果数据量大于等于阈值,则删除表头节点
            if (hashMap.size() >= cacheSize) {
                String headKey = head.key;
                deleteNode(head);
                hashMap.remove(headKey);
            }
            // 表尾插入新节点
            Node newNode = new Node(key, value);
            addNode(newNode);
            hashMap.put(key, newNode);

        } else {
            // node 存在,则更新节点
            node.value = value;
            refreshNode(node);
        }
    }

    /**
     * 更新Node位置
     */
    private void refreshNode(Node node) {
        // 如果访问的是尾节点,无需移动节点
        if (node == tail) {
            return;
        }
        // 移除节点
        deleteNode(node);
        // 重新插入节点
        addNode(node);
    }

    /**
     * 移除节点
     */
    private void deleteNode(Node node) {
        if (node == tail) {
            // 如果该节点是尾结点,则移除尾节点
            tail = tail.prev;
        } else if (node == head) {
            // 如果该节点是头结点,则移除头节点
            head = head.next;
        } else {
            // 如果该节点是中间结点
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }
    }

    /**
     * 插入一个节点
     */
    private void addNode(Node node) {
        if (tail != null) {
            tail.next = node;
            node.prev = tail;
            node.next = null;
        }
        // 该节点为尾节点
        tail = node;
        if (head == null) {
            head = node;
        }
    }

}

参考资料

漫画:什么是LRU算法?

Q.E.D.


吃的苦中苦,卷成王中王🏆