LRU,最近最少使用。如果我们用一个数据结构来实现LRU的话,那么需要满足两个条件,第一个该数据结构需要存储最近使用或未使用的,第二个,需要限制这个数据结构的大小。
我们用LindedHashMap实现LRU,第一需要设置accessOrder,在默认情况下,accessOrder是为false,表示顺序为插入顺序。为true时,表示会根据访问顺序排序(在get时),最新使用的排在尾巴上。初始化设置accessOrder的源码如下:
/**
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param accessOrder the ordering mode - <tt>true</tt> for
* access-order, <tt>false</tt> for insertion-order
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
在get或者方法里都会调用afterNodeAccess,这个方法就是把当前node放在尾巴上(看不明白没关系,看到这个注释了吗,// move node to last,hhhhhh)
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
这样,设置了accessOrder=true,我们就满足了第一个条件,此时LinkedHashMap的顺序是访问顺序,最新使用的在后面。然后我们需要重写removeEldestEntry这个方法,这个方面默认是返回false,表示不需要移除第一个,需要重写它,表示需要移除第一个,即最近未使用的。在源码里也有说明:
/**
*.........
* <p>Sample use: this override will allow the map to grow up to 100
* entries and then delete the eldest entry each time a new entry is
* added, maintaining a steady state of 100 entries.
* <pre>
* private static final int MAX_ENTRIES = 100;
*
* protected boolean removeEldestEntry(Map.Entry eldest) {
* return size() > MAX_ENTRIES;
* }
* </pre>
*
*...........
*/
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
所以我们只需要这样重写,当Map的最大值大于设定的最大值时,需要移除第一个:
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer,Integer>(maxSize, 0.75f, true){
@Override
protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest) {
return this.size() > maxSize;
}
};
这样重写就好啦,当我们每次使用LinkedHashMap里的元素时,使用的这个元素就会放在最后面,最长未使用的就会在前面。当新加的元素超过Map的大小时,就会移除第一个,然后把新加的放在最后面。
附上测试代码和结果:
/**
* main
*
* @description 测试
* @author zhui
* @date 2020-12-23 14:09
* @version v1.0.0
*/
public class main {
private static ExecutorService executorService;
public static void main(String[] args) throws InterruptedException{
createThreadPool();
LRUByLinkedHashMap(3);
}
/**
* @description 创建一个单线程,用来测试
* @return void
**/
public static void createThreadPool(){
executorService = Executors.newSingleThreadExecutor();
}
/**
* @description 测试代码
* @return void
**/
public static void LRUByLinkedHashMap(int maxSize) throws InterruptedException{
// LRU的LinkedHashMap
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<Integer,Integer>(maxSize, 0.75f, true){
@Override
protected boolean removeEldestEntry(Map.Entry<Integer,Integer> eldest) {
return this.size() > maxSize;
}
};
for (int i = 1; i <= maxSize; i++) {
map.put(i, 0);
}
System.out.println("初始map:" +map);
// 测试
for (int i = 1; i <= 10; i++){
int temp = randInt(1,100);
if (temp % 2 == 0) {
// 对map的key随机get
randomGetValue(maxSize, map);
}else {
// 对map进行随机put
randomPutValue(maxSize,map);
}
}
// 关闭单线程
executorService.shutdown();
}
/**
* @description 对map随机取值,观察其顺序变化
* @param maxSize map大小,作为key取值范围
* @param map map
**/
public static void randomGetValue(int maxSize, Map<Integer, Integer> map) throws InterruptedException{
final CountDownLatch end = new CountDownLatch(maxSize);
for(int i=1; i<= maxSize; i++){
executorService.execute(() -> {
int key = randInt(1, maxSize);
forGetValueByRandom(key, map);
System.out.println("key=" + key + ",随机取值后map:" + map);
end.countDown();
});
}
end.await();
}
/**
* @description 给map新put 1个键值对,观察剩下的map变化
* @param maxSize map的size大小,决定新键值对的下限
* @param map map
* @return void
**/
public static void randomPutValue(int maxSize, Map<Integer, Integer> map){
executorService.execute(() -> {
int key = randInt(maxSize+1, 10);
forGetValueByRandom(key, map);
System.out.println("插入" + key + "后,map:" + map);
});
}
/**
* @description 根据key,随机对这个key进行get1次,没有则添加一个键值对,同时更新其value
* @param key key
* @param map map
* @return void
**/
public static void forGetValueByRandom(int key, Map<Integer, Integer> map){
Integer num = map.getOrDefault(key, 0);
map.put(key, num + 1);
}
/**
* @description 在一个范围内随机取值
* @param min 随机取值最小值
* @param max 随机取值最大值
* @return int
**/
public static int randInt(int min, int max) {
Random rand = new Random();
int randomNum = rand.nextInt((max - min) + 1) + min;
return randomNum;
}
}
然后我们看些结果,是否和预期一样呢?可以看到新加的元素或者刚使用的元素,都会在最后面,简直完美!
初始map:{1=0, 2=0, 3=0}
插入9后,map:{2=0, 3=0, 9=1} //1被移除了
key=3,随机取值后map:{2=0, 9=1, 3=1} //3放到最后面
key=1,随机取值后map:{9=1, 3=1, 1=1} //2被移除,1在最后面
key=3,随机取值后map:{9=1, 1=1, 3=2} //3在最后面
插入8后,map:{1=1, 3=2, 8=1} //8在最后面
插入7后,map:{3=2, 8=1, 7=1} //7在最后面
key=3,随机取值后map:{8=1, 7=1, 3=3} //3在最后面
key=3,随机取值后map:{8=1, 7=1, 3=4} //3在最后面
key=2,随机取值后map:{7=1, 3=4, 2=1} //2在最后面
key=1,随机取值后map:{3=4, 2=1, 1=1} //1在最后面
key=1,随机取值后map:{3=4, 2=1, 1=2} //1在最后面
key=2,随机取值后map:{3=4, 1=2, 2=2} //2在最后面
key=2,随机取值后map:{3=4, 1=2, 2=3} //2在最后面
key=1,随机取值后map:{3=4, 2=3, 1=3} //1在最后面
key=1,随机取值后map:{3=4, 2=3, 1=4} //1在最后面
插入8后,map:{2=3, 1=4, 8=1} //8在最后面
插入4后,map:{1=4, 8=1, 4=1} //4在最后面
插入6后,map:{8=1, 4=1, 6=1} //6在最后面
来源:oschina
链接:https://my.oschina.net/u/4897237/blog/4835359