需求:设计一个缓存类,利用LRU算法机制实现插入和删除数据。
分析
关于LRU缓存在乐扣中的解答是通过Java API中LinkedHashMap这个类来实现,实现步骤如下:
1.在定义的LRU缓存类中声明一个缓存容量和一个LinkedHashMap类型的变量
2.在这个类的构造方法中给这个类的容量大小赋值,构造一个空的 LinkedHashMap实例,具有指定的初始容量(initialCapacity),负载因子(loadFactor)和排序模式(accessOrder)。
- initialCapacity:容量大小,一般默认为16
- loadFactor:就是容量扩容的一个阈值。更直白一些,如果我们把负载因子设置为0.5f,初始的容量是12,那么当容量到达6时就会开始扩容。文档给定的默认值就是0.75f,这是结合空间和时间利用率得出的结论。
- accessOrder:排序模式有两种:true:代表访问模式;false:代表插入模式,这里用true,就表示双链表在头插法插入数据,当再次被访问时又会放到最前面的结点上,这是符合LRU缓存机制的。具体这两者的不同,请参考:关于LRU缓存机制中
accessOrder不同排序模式的对比(含代码)
3.书写一个匿名内部类,在里面重写删除老的键值对方法removeEldestEntry。参考Java API文档的LinkedHashMap类。
4.继续在这个类里书写put方法,参考HasMap的put方法。
5.书写getOrDefault方法,获取指定key值的value,若不存在则返回-1
下面是上述流程的代码实现:
public class LRUdemo2 {
int capacity;
LinkedHashMap<Integer, Integer> linkedhashmap;
public LRUdemo2(int capacity) {
this.capacity = capacity;
linkedhashmap = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return linkedhashmap.size() > capacity;
}
};
}
public void put(int key, int value) {
linkedhashmap.put(key, value);
}
public int get(int key) {
return linkedhashmap.getOrDefault(key, -1);
}
测试代码:
public static void main(String[] args) {
LRUdemo2 lru = new LRUdemo2(2);
lru.put(1, 1);// (head<=>1<=>tail)
lru.put(2, 2);// (head<=>2<=>1<=>tail)
System.out.println(lru.get(1));// 返回key=1,并把key=1的结点添加到head结点后面(head<=>1<=>2<=>tail)
lru.put(3, 3);// (head<=>3<=>1<=>tail)
System.out.println(lru.get(2));// key=2,不存在,不进行操作,等待添加进去
lru.put(4, 4);// (head<=>4<=>3<=>tail)
System.out.println(lru.get(1));// key=1,不存在,不进行操作,等待添加进去
System.out.println(lru.get(3));// 添加后(head<=>3<=>4<=>tail)
System.out.println(lru.get(4));// 添加后(head<=>4<=>3<=>tail)
}
另外最后,还有一种思路更好理解的方法,也更加便于实践,那就是手动写双链表+hashmap的结构,这里希望一并清楚的将思路介绍给大家,权当练习思路。
1)定义一个链表类,里面有key、value、表示前一个和后一个的链表结点。
2)在URLDemo1中声明一个HasMap和存储的最大限度,键为包裹类Integer,值为链表类型,构造并实例化两个结点表示这个双向链表的头和尾,并在构造方法中初始化头尾关系
3)书写put方法,先判断要放入的数据是不是已经在这个hashmap中存放了,如果没存放 { 就继续判断,这个hashmap的容量是不是等于我们规定的最大限度,如果超过了就先删除双向双向链表的最后一个结点,接着继续将要插入数据放入双向链表的头部,} 如果存放了就需要修改后再次放到双向链表的头部。
4)书写get方法,如果map中存在这个数据,直接将其的值返回出来,并且还需要将这个数据放到双向链表的头部,如果map中不存在则返回-1。