LRU缓存机制

方法一(直接使用LinkedHashMap)

Java中的LinkedHashMap不仅满足前两点要求,还提供了一个removeEldestEntry(Map.Entry<K,V> eldest)方法,可以删除最久未访问的结点,非常适合用来解这道题。

代码:

class LRUCache extends LinkedHashMap<Integer,Integer>{
    private int capacity;
    public LRUCache(int capacity) {
        //调用父类中的构造方法创建一个LinkedHashMap,设置其容量为capacity,loadFactor为0.75,并开启accessOrder为true
        super(capacity, 0.75F, true);
        this.capacity = capacity;
    }

    public int get(int key) {
        //若key存在,返回对应value值;若key不存在,返回-1
        return super.getOrDefault(key,-1);
    }
    
    public void put(int key, int value) {
        super.put(key,value);
    }
    protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest){
        //若返回的结果为true,则执行removeEntryForKey方法删除eldest entry
        return size() > capacity;
    }
}

说明:

创建LinkedHashMap时,用到了loadFactor和accessOrder。loadFactor(负载因子),是用来控制数组存放数据的疏密程度的参数。

loadFactor越趋近于1,数组中存放的数据就越多、越密,会导致查找元素效率降低。而loadFactor越趋近于0,数组中存放的数据就越少、越疏,会导致数组的利用率降低。loadFactor的默认值为0.75f,是官方给出的一个比较好的临界值。

至于accessOrder,介绍它之前,先来看一下LinkedHashMap的底层构造:

linkedHashMap的底层结构是数组+单向链表,此外还维护一条逻辑上的双向链表

LinkedHashMap的底层数据结构和HashMap一样,采用数组+单向链表(JDK1.8之前)的形式,只是在节点Entry中增加了before和after变量,用于维护一个双向链表来保存LinkedHashMap的存储顺序。

而这个双向链表又提供了两种排序方法:插入顺序和访问顺序。

当accessOrder为false时(默认情况),linkedHashMap只会按插入顺序维护双向链表。而开启了accessOrder之后,linkedHashMap就会把每一次对结点的访问也作为标准来进行排序。也就是说,在每次插入结点/访问结点的时候,都会将相应结点移动到双向链表的尾部,从而达到按访问顺序进行排序的目的。

所以这里需将accessOrder参数开启为true。

可以看到,用LinkedHashMap实现LRU非常方便,它内部提供了我们需要的所有操作。但走这条捷径并不是很有利于我们深刻理解LRU的缓存机制,接下来我们就尝试使用最简单的数据结构来实现。


 

方法二(哈希表+双向链表)

思路:不依赖LinkedHashMap,只使用最基础的哈希表,再维护一个双向链表,提供以下几种方法即可:

  • 插入结点到头部
  • 移动结点到头部
  • 删除结点
  • 删除尾部结点

代码:

import java.util.Hashtable;
class LRUCache {
    class DLinkedNode{
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;
    }
    //插入结点到头部
    private void addNode(DLinkedNode node){
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }
    //删除结点
    private void removeNode(DLinkedNode node){
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
    //删除尾部结点
    private void removeLastNode(){
        removeNode(tail.prev);
    }
    //移动结点到头部
    private void removeToHead(DLinkedNode node){
        removeNode(node);
        addNode(node);
    }
    private Hashtable<Integer,DLinkedNode> cache = new Hashtable<Integer,DLinkedNode>();
    private int capacity,size;
    private DLinkedNode head,tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        head = new DLinkedNode();
        tail = new DLinkedNode();
        //将头结点和尾结点相连
        head.next = tail;
        tail.prev = head;
    }
    
    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if(node==null){
            return -1;
        }
        //若key值存在,移动该结点到头部
        removeToHead(node);
        return node.value;
    }
    
    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        //若key值不存在,直接插入结点到头部,再判断当前容量是否大于capacity,如果是,就删除尾部结点
        if(node==null){
            DLinkedNode newNode = new DLinkedNode();
            newNode.key = key;
            newNode.value = value;
            cache.put(key,newNode);
            addNode(newNode);
            size++;
            if(size>capacity){
                DLinkedNode last = tail.prev;
                cache.remove(last.key);
                removeLastNode();
                size--;
            }
        }else{
            //若key值存在,则更新value值,并移动该结点到链表头部
            node.value = value;
            removeToHead(node);
        }
    }
}

总结:这就是LRU的底层实现了,get和put方法的时间复杂度都是O(1),执行效率非常高。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值