两种方法手写LRU(LinkedHashMap&&map+双链表)

本文介绍了一种基于HashMap和双向链表实现的LRU缓存机制,通过具体代码展示了如何进行节点操作、缓存更新及容量控制,适用于内存敏感型应用场景。

前面注释掉的部分是简单的用LinkedHashMap写的LRU,属于没有技术含量,也不能很好的理解LRU.

import java.util.HashMap;

/**
 * @ClassName LRU 借助LinkedHashMap,重写removeEldestEntry,调用LinkedHashMap的size()方法来判断存入的元素个数。
 * @Description TODO
 * @Author Handoking
 * @Date 2019/7/17 8:54
 **/
//public class LRU<K,V> extends LinkedHashMap<K,V> {
//    int size =5;
//    public LRU(int size){
//        super(16,0.75f,true);
//        this.size = size;
//    }
//
//    @Override
//    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
//        return size()>size;
//    }
//}

/**
 * @ClassName LRU 使用map和双链表实现。
 * @Description TODO
 * @Author Handoking
 * @Date 2019/7/17 8:54
 **/
public class LRU<K,V>{
    class CacheNode {
        CacheNode pre;
        CacheNode next;
        Object key;
        Object value;

        CacheNode(Object key, Object value) {
            this.key = key;
            this.value = value;
            this.pre = null;
            this.next =null;
        }
        CacheNode(){

        }
    }
    private int cacheSize;
    private HashMap<K,CacheNode> cache;
    private CacheNode head;
    private CacheNode tail;
    public LRU(int size){
        this.cacheSize = size;
        cache = new HashMap<>();
    }
    public Object put(K k,V v){
        CacheNode node = cache.get(k);
        Object temp=-11111;
        if(node==null){
            //当前节点不存在,判断缓存是否已经存满,若存满,那么删除尾节点,同时删除map中存储的数据。
            if(cache.size()>=cacheSize){
                cache.remove(tail.key);
                delectTail();
            }
            //创建新结点
            node =new CacheNode();
            node.key = k;

        }else{
            temp = node.value;
        }
        //无论存在不存在,都需要将此节点移动到链表头,因此统一合并操作。
        node.value = v;
        cache.put(k,node);
        moveToHead(node);
        //返回旧值
        if(!temp.equals(-11111)){
            return temp;
        }
       return -1;
    }
    public Object get(K k){
        //get的操作需要判断,如果结点存在那么返回值并将此节点移动到链表头部
        //如果不存在,返回空值
        CacheNode node  = cache.get(k);
        if(node==null){
            return null;
        }
        moveToHead(node);
        return node.value;
    }
    private void delectTail(){
        if(tail!=null){
            tail = tail.pre;
            if (tail==null){
                //此时tail为空值时,说明头节点和尾节点为同一节点,删除后整个链表为空
                head=null;
            }else{
                tail.next =null;
            }
        }

    }
    private void moveToHead(CacheNode node){
        //如果是头节点,那么不需要操作
        if(node==head){
            return;
        }
        //如果不是头节点,
        if (node==tail){
            tail = tail.pre;
            tail.next=null;
        }
        if(node.next!=null){
            node.next.pre = node.pre;
        }
        if(node.pre!=null){
            node.pre.next = node.next;
        }
        //至关重要,如果是首次插入,那么head为空
        if(head==null||tail==null){
            head=tail =node;
            return;
        }
        node.next = head;
        head.pre = node;
        head =node;
        head.pre = null;


    }
    public Object remove(K k){
        //如果值存在,链表中调整前后的指针删除节点,cache中使用map直接删除
        CacheNode node = cache.get(k);
        if (node!=null){
            if(node==head){
                head =node.next;
                head.pre =null;
            }
            if(node ==tail){
                tail = node.pre;
                tail.next=null;
            }
            if(node.pre!=null){
                node.pre.next = node.next;
            }
            if (node.next!=null) {
                node.next.pre = node.pre;
            }

        }
        //hashMap的remove方法会值在不存在时,返回空值,存在时返回被删除的值,因此可以合并到这里一并删除缓存里的值
        return cache.remove(k);
    }
    public void clear(){
        //链表清空,map清空
        head =null;
        tail =null;
        cache.clear();
    }
    //测试
    public static void main(String[] args) {

        LRU<Integer,String> lru = new LRU<>(3);

        lru.put(1, "a");
        lru.put(2, "b");
        lru.put(3, "c");
        lru.get(1);
        lru.put(4, "d");
        System.out.println( lru.put(1, "aa"));
        lru.put(2, "bb");
        lru.put(5, "e");
        lru.get(1);
        System.out.println(lru.remove(11));
        lru.remove(1);
        lru.put(1, "aaa");
    }
}
### LRU缓存淘汰算法及其实现 LRU(Least Recently Used)是一种常见的缓存淘汰策略,其核心思想是移除最长时间未被使用的数据项。为了实现在 **O(1)** 时间复杂度下完成 `get` 和 `put` 操作,通常会结合哈希表和双向链表来构建自定义的 LRUCache 数据结构。 #### 基于 LinkedHashMap 的实现 Java 中的 `LinkedHashMap` 是一种内置支持顺序特性的哈希映射容器,默认可以按照插入顺序或者访问顺序维护键值对的关系。通过重 `removeEldestEntry()` 方法,可以在容量超过指定大小时自动移除最早插入或最近最少使用的条目[^1]。 以下是基于 Java 的 `LinkedHashMap` 实现: ```java import java.util.LinkedHashMap; import java.util.Map; public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); // accessOrder=true 表示按访问顺序排列 this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; // 超过容量则移除最早的条目 } } ``` 上述代码利用了 `LinkedHashMap` 提供的功能,简化了手动管理双向链表的过程。然而,在实际面试场景中,可能不会允许直接使用此类工具类,而是要求完全手工实现整个逻辑。 --- #### 手工实现 O(1) 时间复杂度的 LRUCache 当无法依赖现有库函数时,可以通过组合 **哈希表** 和 **双向链表** 来达到目标效果。具体来说: - 使用哈希表快速定位某个节点是否存在; - 双向链表用来记录各个节点的访问次序,靠近头部的是较早访问过的元素,而尾部则是最新操作的对象。 下面是完整的 Java 版本的手动实现方案: ```java class Node<K, V> { K key; V value; Node<K, V> prev; Node<K, V> next; public Node(K key, V value) { this.key = key; this.value = value; } } public class CustomLRUCache<K, V> { private Map<K, Node<K, V>> cacheMap; private int capacity; private Node<K, V> head; // 最久未使用的节点 private Node<K, V> tail; // 最近使用的节点 public CustomLRUCache(int capacity) { this.cacheMap = new HashMap<>(); this.capacity = capacity; this.head = null; this.tail = null; } public V get(K key) { if (!cacheMap.containsKey(key)) { return null; } Node<K, V> node = cacheMap.get(key); moveToTail(node); // 将当前节点移到队列末尾表示刚访问过它 return node.value; } public void put(K key, V value) { if (capacity <= 0) return; if (cacheMap.containsKey(key)) { // 如果已存在,则更新值并将该节点移动到队列末尾 Node<K, V> existingNode = cacheMap.get(key); existingNode.value = value; moveToTail(existingNode); } else { // 否则创建新的节点并加入缓存 if (cacheMap.size() >= capacity) { // 若超出了最大容量,则删除头结点对应的旧数据 evictHead(); } Node<K, V> newNode = new Node<>(key, value); addToTail(newNode); // 新增节点放到最后面 cacheMap.put(key, newNode); } } private void moveToTail(Node<K, V> node) { if (node == tail) return; // 已经处于尾巴位置无需调整 if (node.prev != null) { node.prev.next = node.next; } if (node.next != null) { node.next.prev = node.prev; } if (head == node) { // 更新头指针指向下一个节点 head = node.next; } addAsNewTail(node); // 把这个节点重新挂载到最后作为最新的访问者 } private void addAsNewTail(Node<K, V> node) { if (tail != null) { tail.next = node; node.prev = tail; } tail = node; if (head == null) { // 初始状态设置头等于尾 head = node; } node.next = null; // 确保新尾节点没有后续链接关系 } private void evictHead() { if (head == null) return; cacheMap.remove(head.key); if (head == tail) { // 单一节点情况特殊处理 head = null; tail = null; } else { head = head.next; head.prev = null; } } } ``` 以上代码实现了基本功能需求,并且每次调用 `get` 或 `put` 方法都能保持平均时间复杂度为 **O(1)** 。这是因为所有的关键操作——查找、插入、删除——都可以借助哈希表与双端链表高效完成[^4]。 --- #### 总结 无论是采用标准库中的高级组件还是自行搭建基础架构,理解底层原理始终至关重要。对于技术岗位候选人而言,能够清晰阐述为何选用特定设计方案以及如何平衡性能指标显得尤为重要。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值