• Java实现LRU Cache的方法


    1.双向链表 LinkedList
    package LRU;

    import java.util.Iterator;
    import java.util.LinkedList;

    /**
    * LRU: 最近最少使用算法 。 最近最少使用的元素,在接下来一段时间内,被访问的概率也很低。
    * 即最近被使用的元素,在接下来一段时间内,被访问概率较高。
    * <p>
    * 用链表的结构:
    * 链表尾表示最近被访问的元素,越靠近链表头表示越早之前被访问的元素
    * <p>
    * 插入一个元素,cache 不满,插到链表尾,满,移除cache链头元素再插入链表尾
    * 访问一个元素,从链表尾部开始遍历, 访问到之后,将其从原位置删除,重新加入链表尾部
    * <p>
    * 实现1:用双向链表实现。
    * put、get 时间复杂度:O(n) 效率低
    * <p>
    * created by Ethan-Walker on 2019/2/16
    */
    public class LRUCache {

    LinkedList<Node> cache;

    int capacity;

    public LRUCache(int capacity) {
    this.cache = new LinkedList<>();
    this.capacity = capacity;
    }

    // -1 表示没找到
    public int get(int key) {
    Iterator<Node> iterator = cache.descendingIterator();
    int result = -1;
    while (iterator.hasNext()) {
    Node node = iterator.next();
    if (node.key == key) {
    result = node.val;
    iterator.remove();
    put(key, result); //添加到链表尾部
    break;
    }
    }
    return result;
    }

    public void put(int key, int value) {
    //先遍历查找是否有key 的元素, 有则删除,重新添加到链尾
    Iterator<Node> iterator = cache.iterator();
    while (iterator.hasNext()) {
    Node node = iterator.next();
    if (node.key == key) {
    iterator.remove();
    break;
    }
    }

    if (capacity == cache.size()) {
    //缓存已满,删除一个 最近最少访问的元素(链表头)
    cache.removeFirst();
    }
    cache.add(new Node(key, value));
    }


    class Node {
    int key;
    int val;

    public Node(int key, int val) {
    this.key = key;
    this.val = val;
    }
    }

    }

    2. LinkedHashMap
    package LRU;

    import java.util.Iterator;
    import java.util.LinkedHashMap;
    import java.util.Set;

    /**
    * LinkedHashMap 实现
    * put /get 操作 O(1)
    * 特殊情况:缓存已满,需要删除链表头
    * created by Ethan-Walker on 2019/2/16
    */
    public class LRUCache2 {
    LinkedHashMap<Integer, Integer> cache;
    int capacity;

    public LRUCache2(int capacity) {
    cache = new LinkedHashMap<>(capacity);
    this.capacity = capacity;
    }

    public int get(int key) {
    if (!cache.containsKey(key)) return -1;

    int val = cache.get(key);
    cache.remove(key); // 从链表中删除
    cache.put(key, val); // 添加到链尾

    return val;

    }

    public void put(int key, int value) {
    if (cache.containsKey(key)) {
    cache.remove(key); // 已经存在,链表中删除
    }

    if (capacity == cache.size()) {
    // cache 已满,删除链表头
    Set<Integer> keySet = cache.keySet();
    Iterator<Integer> iterator = keySet.iterator();
    cache.remove(iterator.next());
    }
    cache.put(key, value);// 添加到链尾
    }
    }
    借助 LinkedHashMap 已经实现的 删除最近最少使用的元素 方法 removeEldestEntry

    /**
    * LinkedHashMap 本身内部有一个触发条件则自动执行的方法:删除最老元素(最近最少使用的元素)
    * 由于最近最少使用元素是 LinkedHashMap 内部处理
    * 故我们不再需要维护 最近访问元素放在链尾,get 时直接访问/ put 时直接存储
    * created by Ethan-Walker on 2019/2/16
    */
    class LRUCache3 {
    private Map<Integer, Integer> map;
    private final int capacity;

    public LRUCache3(int capacity) {
    this.capacity = capacity;
    map = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > capacity; // 容量大于capacity 时就删除
    }
    };
    }
    public int get(int key) {
    return map.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
    map.put(key, value);
    }
    }
    3. 手动实现
    HashMap 中存储每个key 对应的节点Node<key,value>, Node节点之间再通过 prev、next 链接,实现有序的双向链表

    package LRU;

    import java.util.HashMap;

    /**
    * created by Ethan-Walker on 2019/2/16
    */
    public class LRUCacheByself {

    // 双向链表节点结构
    private class Node {
    public Node pre;
    public Node next;
    public int key;
    public int val;

    public Node(int k, int v) {
    this.key = k;
    this.val = v;
    this.pre = null;
    this.next = null;
    }
    }

    // 双向链表 头部是最老的
    private class DoublyLinkedList {
    public Node head;
    public Node tail;

    public DoublyLinkedList() {
    this.head = null;
    this.tail = null;
    }

    public void moveToTail(Node n) {
    // 将节点移动至尾部
    if (n == null || n == tail) return;
    if (head == n) {
    head = n.next;
    head.pre = null;
    } else {
    n.pre.next = n.next;
    n.next.pre = n.pre;
    }

    tail.next = n;
    n.pre = tail;
    n.next = null;
    tail = tail.next;
    }

    public void addToTail(Node n) {
    if (n == null) return;
    // 添加新的节点
    if (head == null) {
    head = n;
    tail = n;
    } else {
    tail.next = n;
    n.pre = tail;
    tail = n;
    }
    }

    public Node removeHead() {
    // 删除头部(最老的)节点
    if (head == null) return null;
    Node n = head;
    if (head == tail) {
    head = null;
    tail = null;
    } else {
    head = head.next;
    head.pre = null;
    }
    return n;
    }
    }

    private DoublyLinkedList list;
    private HashMap<Integer, Node> map;
    private int capacity;

    public LRUCacheByself(int capacity) {
    this.list = new DoublyLinkedList();
    this.map = new HashMap<>();
    this.capacity = capacity;
    }

    public int get(int key) {
    if (!map.containsKey(key)) {
    return -1;
    }
    Node n = map.get(key);
    list.moveToTail(n);
    return n.val;
    }

    public void put(int key, int value) {
    if (!map.containsKey(key)) {
    Node n = new Node(key, value);
    map.put(key, n);
    list.addToTail(n);

    if (map.size() > capacity) {
    Node rmv = list.removeHead();
    map.remove(rmv.key);
    }
    } else {
    Node n = map.get(key);
    n.val = value;
    list.moveToTail(n);
    }
    }
    }

  • 相关阅读:
    【整理】数组面试题集锦
    【整理】二叉树面试题集锦
    【转】C++怎么设计只能在堆或者栈分配空间的类以及定义一个不能被继承的类
    【转】面试题:最长回文子串
    【转】后缀数组求最长重复子串
    【转】linux硬链接与软链接
    【转】随机函数面试题
    【转】C++ 重载、覆盖和隐藏
    分类算法评估指标
    Pandas_对某列的内容分列
  • 原文地址:https://www.cnblogs.com/shoshana-kong/p/14079077.html
Copyright © 2020-2023  润新知