散列表+双向链表实现LRU算法

缓存是一种提高数据读取性能的技术,比如常见的CPU缓存,数据库缓存以及浏览器缓存。
缓存的大小有限,当缓存被用满时,哪些数据应该被清理出去,哪些数据应该被保留?
缓存淘汰策略,常见的有三种,先进先出策略FIFO,最少使用策略LFU,最近最少使用策略LRULeast Recently Used)。

散列表+双向链表实现LRU算法
我们需要维护一个按照访问时间从大到小有序排列的链表结构。
因为缓存大小有限,当缓存空间不够需要淘汰一个数据时,我们直接将链表头部的节点删除。

缓存的主要操作

  • 向缓存中添加一个数据
  • 从缓存中删除一个数据
  • 在缓存中查找一个数据

具体的数据结构
散列表+双向链表实现LRU算法_第1张图片
下面我们来具体来看看前面讲到缓存的三个操作

  • 在缓存中查找一个数据
    散列表中查找的时间复杂度接近O(1),所以通过散列表,我们可以很快的在缓存中查找到数据。当找到数据之后,还需要将它移到双向链表的尾部。
  • 从缓存中删除一个数据
    我们需要找到数据所在的节点,然后将节点删除。通过散列表,我们可以在O(1)的时间复杂度里找到要删除的节点,因为我们使用的是双向链表,可以通过前驱指针O(1)的时间复杂度获取到前驱节点,删除节点的时间复杂度为O(1)。
  • 向缓存中添加一个数据
    先看数据是否在缓存中,如果已经在缓存中,将其移到到双向链表的尾部;如果不在其中,需要看缓存有没有满,如果没有满,将数据直接添加到链表的尾部,如果缓存已满,则需要删除双向链表的头节点,再讲数据添加到链表的尾部。

具体的代码实现

package com.luochao.algo.cache;

import java.util.HashMap;

/**
 * @author luoChao
 * @create_date 2019-08-19 10:08
 * @desc 我们需要维护一个按照访问时间从大到小有序排列的链表结构,
 * 因为缓存大小有限,当需要淘汰一个数据时,我们直接将链表头部的节点删除
 */

public class LruCache {

    private Node head;

    private Node end;

    // 缓存上限
    private int cacheLimit;

    // 哈希表
    private HashMap<String, Node> hashMap;


    public LruCache(int cacheLimit) {
        this.cacheLimit = cacheLimit;
        hashMap = new HashMap<>();
    }

    /**
     * 从缓存中查找一个数据,然后将其移到双向链表的尾部
     * @param key key
     * @return value
     */
    public String get(String key) {
        Node node = hashMap.get(key);
        if (node == null) {
            return null;
        }
        refreshNode(node);
        return node.value;
    }

    /**
     * 从缓存中移除一个数据
     * @param key key
     */
    public void remove(String key) {
        Node node = hashMap.get(key);
        if (node != null) {
            removeNode(node);
            hashMap.remove(key);
        }
    }


    /**
     * 向缓存中添加一个数据
     * 缓存中存在,更新缓存的值,将其移到双向链表的尾部
     * 缓存中不存在,判断缓存是否已满
     * 已满,将链表的头节点删除,将数据添加的链表尾部
     * 未满,直接将数据插入到链表尾部
     * @param key key
     * @param value value
     */
    public void put(String key,String value) {
        Node node = hashMap.get(key);
        if (node != null) {
            node.value = value;
            refreshNode(node);
        } else {
            if (hashMap.size() >= cacheLimit) {
                String oldKey = removeNode(head);
                hashMap.remove(oldKey);
            }
            node = new Node(key, value);
            addNode(node);
            hashMap.put(key, node);
        }
    }

    /**
     * 刷新节点位置
     * @param node 节点
     */
    private void refreshNode(Node node) {
        // 尾节点不需要移动
        if (node == end) {
            return;
        }
        // 移除节点
        removeNode(node);
        // 重新插入到双向链表的尾部
        addNode(node);
    }

    /**
     * 双向链表尾部插入节点
     * @param node
     */
    private void addNode(Node node) {
        if (end != null) {
            end.next = node;
            node.pre = end;
            node.next = null;
        }
        end = node;
        if (head == null) {
            head = node;
        }
    }

    /**
     * 删除节点
     * @param node 要删除的节点
     */
    private String  removeNode(Node node) {
        if (node == head && node == end) { // 移除唯一的节点
            head = null;
            end = null;
        } else if (node == end){ // 移除尾节点
            end = end.pre;
            end.next = null;
        } else if (node == head) { //移除头节点
            head = head.next;
            head.pre = null;
        } else { // 移除中间节点
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }
        return node.key;
    }

    public void show () {
        Node node = head;
        while (node != null) {
            System.out.print(node.key + "  ");
            node = node.next;
        }
        System.out.println();
    }

    class Node {
        public Node(String key, String value) {
            this.key = key;
            this.value = value;
        }
        Node pre;
        Node next;
        String key;
        String value;
    }

    public static void main(String[] args) {
        LruCache lruCache = new LruCache(5);
        lruCache.put("6","66");
        lruCache.put("1","11");
        lruCache.put("5","5");
        lruCache.put("2","22");
        lruCache.show();
        lruCache.get("3");
        lruCache.put("5","55");
        lruCache.show();
        lruCache.put("3","33");
        lruCache.put("4","44");
        lruCache.get("5");
        lruCache.show();

    }
}

你可能感兴趣的:(数据结构和算法)