LRU cache实现

本文深入探讨LRU缓存的概念,解释其如何通过维护最近最不常用的数据来优化系统性能,并提供基于LinkedHashMap的简单实现方式,包括使用双向链表和哈希表的组合来实现实现O(1)复杂度的操作,如添加、查找和移除元素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引子:

我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本,但是,通过电话本查找还是很费时间的。但是,我们大脑能够记住的东西是一定的,我们只能记住自己最熟悉的,而长时间不熟悉的自然就忘记了。

其实,计算机也用到了同样的一个概念,我们用缓存来存放以前读取的数据,而不是直接丢掉,这样,再次读取的时候,可以直接在缓存里面取,而不用再重新查找一遍,这样系统的反应能力会有很大提高。但是,当我们读取的个数特别大的时候,我们不可能把所有已经读取的数据都放在缓存里,毕竟内存大小是一定的,我们一般把最近常读取的放在缓存里(相当于我们把最近联系的朋友的姓名和电话放在大脑里一样)。现在,我们就来研究这样一种缓存机制。

LRU Cache:

LRU缓存利用了这样的一种思想。LRU是Least Recently Used 的缩写,翻译过来就是“最不常使用”,也就是说,LRU缓存把最不常使用的数据移除,让给最新读取的数据。大多数情况下,最常读取的,也是读取次数最多的,所以,利用LRU缓存,我们能够提高系统的performance. LRU cache是非常频繁出现的一道面试题,一般来讲,当我们问到这道题时,面试官往往想得到的答案是利用 doubly linked list + hashtable 实现 LRU Cache, 那么我们现在就来讲一讲如何利用doubly linked list + hashtable实现LRU Cache的。

对于LRU cache,往往会有以下要求:

1. 假设Cache里面的 entry 都是按照序列保存的,那么,对于新的entry,我们把它放置在最前面。

2. 如果一个entry已经存在,我们再次访问到该entry的时候,我们需要把它放在cache的最前面。

3. 当cache满了的时候,需要把最后一个entry 从cache里面移除出去,然后再往里插入 entry。

4. 以上所有的操作复杂度必须为 O(1)。

对于操作复杂度,一旦看到要求为O(1), 一般我们都会立刻想到 hashtable, 所以,为了实现“顺序”的要求,我们需要有一个链表来连接所有的entry. 所以,在实现时,我们将Cache的所有 entry 都用doubly linked list 连接起来,当一个 entry 被命中之后,就将通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,而想链表后面移动,链表尾则表示最近最少使用的Cache。当需要替换内容时候,链表的最后位置就是最少被命中的位置,我们只需要淘汰链表最后的部分即可。

所谓LRU就是最近最少被使用的意思,LRUCache的目的就是当缓存中的数据量达到一定的程度时,添加新的数据会将最近最少被使用的旧数据删除,以保障Cache中的数据都是经常需要的数据,提高命中率。下面是一个基于linkedhashmap的LRUCache的简单实现,关键是重写LinkedHashMap的removeEldestEntry方法,在LinkedHashMap中该方法默认返回false(LRUCache本身未考虑线程安全的问题)

package test1;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by Administrator on 15-1-21.
 */
public class BasicLRUCache<K, V> {

    private LinkedHashMap<K, V> map;

    private int cacheSize;

    static final int DEFAULT_CACHE_SIZE = 2;

    public BasicLRUCache(){
        this(DEFAULT_CACHE_SIZE);
    }

    public BasicLRUCache(int cacheSize){
        this.cacheSize = cacheSize;
        int hashTableSize = (int)Math.ceil(cacheSize/0.75f) + 1;
        map = new LinkedHashMap<K, V>(hashTableSize, 0.75f, true){

            private static final long serialVersionUID = 1L;

            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                System.out.println("size=" + size() + " cacheSize=" + BasicLRUCache.this.cacheSize);
                return size() > BasicLRUCache.this.cacheSize;
            }
        };

    }

    public V put(K key, V value){
        return map.put(key, value);
    }

    public V get(Object key){
        return map.get(key);
    }

    public static void main(String[] args){
        BasicLRUCache<String, String> lruCache = new BasicLRUCache<String, String>(3);
        lruCache.put("1", "1");
        lruCache.put("2", "2");
        lruCache.put("3", "3");
        lruCache.put("4", "4");
        System.out.println(lruCache.get("1"));
        System.out.println(lruCache.get("2"));
        System.out.println(lruCache.get("3"));
        System.out.println(lruCache.get("4"));
    }

}
"C:\Program Files\Java\jdk1.7.0_67\bin\java" -Didea.launcher.port=7533 "-Didea.launcher.bin.path=D:\Program Files\JetBrains\IntelliJ IDEA 13.1.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.7.0_67\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\jce.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\jfxrt.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\resources.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\rt.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\access-bridge.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.7.0_67\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.7.0_67;D:\IdeaProject\test\dubbo-consumer;D:\IdeaProject\bppfRefactor\bpep-portal;D:\IdeaProject\bppfRefactor\test\target\classes;D:\Program Files\JetBrains\IntelliJ IDEA 13.1.3\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain test1.BasicLRUCache
size=1 cacheSize=3
size=2 cacheSize=3
size=3 cacheSize=3
size=4 cacheSize=3
null
2
3
4

Process finished with exit code 0










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值