目录
不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!
LRU Cache缓存机制
概念
LRU Cache缓存机制即利用LRU算法实现的缓存机制。
LRU算法
概念
是一种缓存淘汰
策略。LRU
全称Least Recently Used,即最近最久未使用
。表示这一块缓存中的数据,已经很长时间没有被使用过了。
原理
- get方法
- 如果加载的是缓存区中
存在
的数据,那么使用
该数据,并将该数据放置在缓存区开头
。 - 如果加载的是缓存区中
不存在
的数据,并且缓存区中还有内存
,那么网络请求
数据,并把数据放入缓存区开头
。 - 如果加载的是缓存区中
不存在
的数据,但是缓存区中已经没有内存
可以去利用了,那么就将根据LRU
原则,将最近最久未使用
的数据删除,回收
缓存区,一般都在缓存区的末尾
。
- put方法
- 数据在缓存中
已存在
,将那个数据移到开头
。 - 数据
不存在
缓存中,缓存空间充足
,放入缓存
中。 - 数据
不存在
缓存中,缓存空间不足
,根据最近最久未使用
原则,删除最后一个
数据,然后将数据放入缓存。
- delete方法
空间不足时使用,根据LRU
原则,删除最后一个数据。
实现
数组
使用这种连续存储
的空间当做缓存区。其中放置的数据对象有大小
、访问时间戳
。每次访问、插入、更新的时候都更新这个时间戳。如果内存不够,需要删除时,删除时间戳最老
的即可。
链表
使用这种链式存储
的空间当做缓存区,比如队列
,双向链表
。将插入、更新、访问的结点放在开头,将最久没使用的放在末尾。所以删除最近最久未使用的数据,只需要删除最后一个结点即可。
实现
- 一个初始化方法,设置缓存区大小,定义数据结构。
- 一个get方法,通过传入key,返回value。
- 一个put方法,通过key,value,将value保存在key的位置。
- 一个delete方法,删除最近最久未使用的数据,回收缓存区。
使用ArrayList实现一个最简单的LruCache(最容易理解)
思路:构造方法定义容量和数据结构ArrayList,put方法向ArrayList加入数据,get方法请求数据,delete方法删除最先加入的数据。
- LruCache类
package com.hnucm;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
public class LruCache {
//定义缓存容量
int maxSize = 0;
//定义数据结构
ArrayList<Integer> list = null;
//构造函数初始化缓存容量和数据结构
public LruCache(int size){
this.maxSize = size;
list = new ArrayList<Integer>();
}
//向缓存中加入数据
public void put(int val) {
//缓存中已存在
if(list.contains(val)) {
//调用数据,将数据移到末尾
list.remove(val);
}else {
//不存在
//容量不足
if(list.size() == maxSize) {
//删除第一个数据
delete();
}
}
//加入缓存中
list.add(val);
}
//请求数据
public void get(int val) {
//缓存中不存在
if(!list.contains(val)) {
System.out.println("缓存中不存在这个数,需要网络请求这个数据!返回的"
+ "数据还需判断缓存是否还有空间存储");
}else {
//存在,使用数据,并移到末尾
list.remove(val);
list.add(val);
}
}
//删除最近最久未使用的缓存
//即第一个数值
public void delete() {
list.remove(list.get(0));
}
}
- 测试数据类
package com.hnucm;
public class Main1 {
public static void main(String[] args) {
LruCache lruCache = new LruCache(2);
lruCache.put(1);
lruCache.put(2);
System.out.println(lruCache.list);
lruCache.get(3);
lruCache.put(3);
System.out.println(lruCache.list);
}
}
使用LinkedHashMap实现LruCache(最经典实现)
LinkedHashMap是双向链表的数据结构,安卓中LruCache源码中就是使用的这个数据结构。
思路:和ArrayList使用的思路大致一样,唯一的不同在于
根据key删除数据,所以找到最先加入的数据,需要使用迭代器。
- LruCache类
package com.hnucm;
import java.security.KeyStore.Entry;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache<K, V> {
// 定义缓存容量
int maxSize = 0;
// 定义数据结构
// 双向链表 + HashMap
LinkedHashMap<K, V> map = null;
// 构造函数初始化
public LruCache(int size) {
this.maxSize = size;
map = new LinkedHashMap<K, V>();
}
/*
* 将数据存入缓存
*/
public void put(K key, V value) {
// 缓存中已存在
if (map.containsKey(key)) {
// 调用数据,将数据移到开头
map.remove(key);
} else {
// 缓存中不存在
// 内存不足
if (map.size() == maxSize) {
//根据LRU原则删除第一个数据
//使用迭代器获取第一个值的key
map.remove(map.entrySet().iterator().next().getKey());
}
}
// 数据放入缓存
map.put(key, value);
}
/*
* 请求数据
*/
public void get(K key) {
//缓存中没有,需要网络请求
if (!map.containsKey(key)) {
System.out.println("缓存中不存在这个数,需要网络请求这个数据!返回的" + "数据还需判断缓存是否还有空间存储");
} else {
//缓存中存在
//map的get会使用它并把它放在末尾
V value = map.get(key);
map.remove(key);
map.put(key,value);
}
}
}
- 测试数据类
package com.hnucm;
public class Main1 {
public static void main(String[] args) {
LruCache<Integer,Integer> lruCache = new LruCache<>(2);
lruCache.put(1,1);
lruCache.put(2,2);
System.out.println(lruCache.map);
lruCache.get(1);
System.out.println(lruCache.map);
lruCache.get(3);
lruCache.put(3,3);
System.out.println(lruCache.map);
lruCache.put(3,4);
System.out.println(lruCache.map);
}
}
使用队列实现LrcCache(类似于ArrayList)
- LruCache类
package com.hnucm;
import java.util.LinkedList;
import java.util.Queue;
public class LruCache{
// 定义缓存容量
int maxSize = 0;
// 定义数据结构
// 队列
Queue<Integer> queue = null;
// 构造函数初始化
public LruCache(int size) {
this.maxSize = size;
queue = new LinkedList<Integer>();
}
/*
* 将数据存入缓存
*/
public void put(int value) {
// 缓存中已存在
if (queue.contains(value)) {
// 调用数据,将数据移到末尾
queue.remove(value);
} else {
// 缓存中不存在
// 内存不足
if (queue.size() == maxSize) {
//根据LRU原则删除第一个数据
queue.poll();
}
}
// 数据放入缓存
queue.offer(value);
}
/*
* 请求数据
*/
public void get(int value) {
//缓存中没有,需要网络请求
if (!queue.contains(value)) {
System.out.println("缓存中不存在这个数,需要网络请求这个数据!返回的" + "数据还需判断缓存是否还有空间存储");
} else {
//缓存中存在
//使用并放入末尾
queue.remove(value);
queue.offer(value);
}
}
}
- 测试数据类
package com.hnucm;
public class Main1 {
public static void main(String[] args) {
LruCache lruCache = new LruCache(2);
lruCache.put(1);
lruCache.put(2);
System.out.println(lruCache.queue);
lruCache.get(1);
System.out.println(lruCache.queue);
lruCache.get(3);
lruCache.put(3);
System.out.println(lruCache.queue);
lruCache.put(4);
System.out.println(lruCache.queue);
}
}
总结
所有的实现几乎都差不多,思路都是最近最久未使用
,只是采用的数据结构
不同。put
就是有空间就直接放入,没空间就去掉最久未使用的缓存,回收空间去放置。get
就是更新数据的使用,将越久没使用的,放在开头,最新使用的在末尾。