Java 实现 LRU 缓存算法

Posted 龙凌云端

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 实现 LRU 缓存算法相关的知识,希望对你有一定的参考价值。

Java 实现 LRU 缓存算法


 

一、什么是 LRU

LRU(Least Recently Used,最近最少使用)是一种缓存算法,其核心思想是将最近最少使用的缓存项移除,以便为更常用的缓存项腾出空间。

在实际应用中,LRU 算法被广泛用于缓存和页面置换。

 

二、Java 实现 LRU 缓存算法

在 Java 中,可以使用 LinkedHashMap 来实现 LRU 缓存算法。

LinkedHashMap 是 HashMap 的一个子类,其内部使用双向链表维护元素的顺序。

具体实现思路如下:

  1. 继承 LinkedHashMap,重写 removeEldestEntry 方法,该方法返回 true 表示需要移除最老的缓存项;
  2. 在构造方法中指定 accessOrder 为 true,这样在访问元素时就会把该元素移动到链表尾部,方便后续查找和移除;
  3. 在访问缓存项时,使用 get 方法获取元素,并通过 removeEldestEntry 方法来判断是否需要移除最老的缓存项;
  4. 在添加缓存项时,使用 put 方法将元素加入 LinkedHashMap 中。

使用 LinkedHashMap 实现 LRU 缓存算法的示例代码如下:

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);
        this.capacity = capacity;
    

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) 
        return size() > capacity;
    

    public static void main(String[] args) 
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        cache.put(1, "one");
        cache.put(2, "two");
        cache.put(3, "three");
        System.out.println(cache); // 1=one, 2=two, 3=three

        cache.get(2);
        System.out.println(cache); // 1=one, 3=three, 2=two

        cache.put(4, "four");
        System.out.println(cache); // 3=three, 2=two, 4=four
    

 

在上面的示例代码中,我们创建了一个 LRUCache 类,继承了 LinkedHashMap,并在构造方法中指定了 accessOrder 为 true。

在 removeEldestEntry 方法中,当缓存项数量超过容量时返回 true,表示需要移除最老的缓存项。

在访问缓存项时,使用 get 方法获取元素,如果缓存项数量超过容量,则会移除最老的缓存项。

在添加缓存项时,使用 put 方法将元素加入 LinkedHashMap 中。

最后,在 main 方法中对缓存进行测试。

 

需要注意的是,在使用 LinkedHashMap 实现 LRU 缓存时,必须指定 accessOrder true,否则 LinkedHashMap 会按照插入顺序维护元素的顺序,而不是访问顺序。

 

LRU 缓存机制及 3 种简单实现

  之前好几次接触到 LRU(Least Recently Used)算法,今天来总结下,并用 Java 和 Python 给出相应的实现。

  LRU是一种缓存替换算法,根据字面意思,就是将最近最少使用的页面或者元素进行替换,将最近最多使用的页面或者元素保持在缓存里。有关缓存的知识后面再仔细研究下。由于缓存的容量大小有限,这才有了LRU之类的缓存算法。还有一些其他的缓存算法,可以参考这个页面

  根据下面的图示进行LRU算法的理解。

技术图片

 

  其中 put 操作用于将最近使用的元素放置在缓存中,get 操作用于获取缓存中元素的值,在 leetcode146 题中规定,如果缓存中没有该元素,则返回 -1。

  一般我们在实现的时候会考虑存储 key-value 的键值对形式,可以用双链表存储 key,HashMap 存储真正需要的值 value,所以真正意义上的缓存应该是指这个HashMap。链表的作用是用来顺序存储 key,当缓存满了,需要删除最远的那个 key 及其 value,此时就需要根据链表找到最远的 value 的 key,从而删除缓存 HashMap中的最远的键值对。

  这里我们用 双链表 + hashMap 以及 LinkedHashMap 、Python 中 OrderedDict 三种方式来实现一个简单的 LRU 机制。

双链表 + hashMap

 1 class Solution 
 2     private LinkedList<Integer> linkedList;
 3     private Map<Integer, Integer> map;
 4 
 5     private int max_size;
 6     private int cur_size = 0;
 7 
 8     public Solution(int capacity) 
 9         linkedList = new LinkedList<>();
10         map = new HashMap<>();
11         this.max_size = capacity;
12     
13 
14     public int get(int key) 
15         if(!map.containsKey(key))
16             return -1;
17         
18 
19         int val = map.get(key);
20         Object o = key;
21         linkedList.remove(o);
22         linkedList.addLast(key);
23 
24         return val;
25     
26 
27     public void put(int key, int value) 
28         /**
29          * 必须先判断是否存在,如果存在,则删除,添加新的;
30          * 不存在,则添加,然后判断添加后的缓存是否超过了容量;若超出,则删除最远元素
31          */
32         if(map.containsKey(key))
33             // 这个put不能省略,即时key存在,若新添加的value更新了,那刚好就将value更新,如果省略,则value更新不了
34             map.put(key, value);
35             Object o = key;
36             linkedList.remove(o);
37             linkedList.addLast(key);
38         else
39             map.put(key, value);
40             cur_size++;
41             linkedList.addLast(key);
42 
43             // listSet.add(key);
44             if(cur_size>max_size)
45                 // 在满了的时候,必须删除最远的那个,对于 HashMap来讲,他不知道哪个最远,所以需要一个有序的链表来记录
46                 int tmp = linkedList.removeFirst();
47                 map.remove(tmp);
48                 cur_size--;
49             
50         
51     
52 

  其中在进行元素删除的时候,链表的时间复杂度是O(n),用 HashMap 进行 key 的查找的时候是O(1)的复杂度。

LinkedHashMap

 1 class Solution 
 2     private LinkedHashMap<Integer, Integer> map;
 3     private int max_size;
 4     private int cur_size;
 5 
 6     public Solution(int capacity) 
 7         map = new LinkedHashMap<>();
 8         this.max_size = capacity;
 9         this.cur_size = 0;
10     
11 
12     public int get(int key) 
13         // 若没有,则返回 -1
14         if(!map.containsKey(key))
15             return -1;
16         
17 
18         int val = map.get(key);
19         map.remove(key);
20         map.put(key, val);
21         return val;
22     
23 
24     public void put(int key, int value) 
25         if(map.containsKey(key))
26             map.remove(key);
27             map.put(key, value);
28         else
29             cur_size++;
30             map.put(key, value);
31             if(cur_size > max_size)
32                 int oldestKey = map.keySet().iterator().next(); // 获取最远的key。
33                 map.remove(oldestKey);
34                 cur_size--;
35             
36         
37     
38 

  LinkedHashMap 本身也是由 双链表 + hashMap 组成的。在缓存满了需要删除最远的元素的时候,是用的 HashMap 里的迭代器来获取最开始进来的key并删除其键值对。

Python OrderedDict

  Python 可以使用这篇文章介绍的 OrderedDict 这一字典子类很轻松的实现 LRU 机制。

 1 class LRUCache:
 2 
 3     def __init__(self, capacity: int):
 4         self.dic = OrderedDict()
 5         self.remain = capacity
 6 
 7 
 8     def get(self, key: int) -> int:
 9         if key not in self.dic:
10             return -1
11         # v = self.dic.pop(key)
12         # self.dic[key] = v
13         self.dic.move_to_end(key, last = True)
14         return self.dic[key]
15 
16     def put(self, key: int, value: int) -> None:
17         if key in self.dic:
18             self.dic.pop(key)
19         else:
20             if self.remain > 0:
21                 self.remain -= 1
22             else:
23                self.dic.popitem(last = False)
24         self.dic[key] = value

 

以上是关于Java 实现 LRU 缓存算法的主要内容,如果未能解决你的问题,请参考以下文章

LRU算法java实现

LRU 缓存机制及 3 种简单实现

缓存淘汰算法之LRU实现

力扣题解 Java语言实现 -- LRU 缓存

力扣题解 Java语言实现 -- LRU 缓存

力扣题解 Java语言实现 -- LRU 缓存