前言
之前我有篇文章是手写实现了LRU,那么如何利用JDK自带的实现呢?本篇带你了解下
提示:需要对LinkedHashMap有了解才行,建议先看源码
一、思路分析
首先我们看下
final boolean accessOrder;
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
只有这个构造方法可以使这个成员变量为true。那么这个是什么意思呢?
默认的LinkedHashMap的访问顺序是添加顺序,当用此构造方法,即可改变结构,比如,添加时顺序为:a,b,c,d,e。当我调用get(a)时,结构变为
b,c,d,e,a
利用此特性再加上
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
我们要重写此方法才行,为什么我们接着分析
我们看下HashMap的put
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//中间的添加过程我就省略了,之前文章分析过
++modCount;//替换的不算
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
afterNodeInsertion(evict);这个方法当我传入参数是true时。
我们来到HashMap的子类LinkedHashMap.
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
evict为true,removeEldestEntry(first)这个我们可以重写使其为true。那么我们就可以删除头节点。
综上我们就利用了两条特性实现了HashMap
- LinkedHashMap的成员变量accessOrder在构造时设置为true,使其按访问顺序排列。访问到的放在末尾。
- 重写removeEldestEntry方法。比如大于缓存最大长度
二、代码
最后贴上完整代码
package HashTest;
import java.util.LinkedHashMap;
import java.util.Map;
public class LRU<K,V> extends LinkedHashMap<K, V> implements Map<K, V> {
private static final long serialVersionUID = 1L;
public LRU(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
/**
* @description 重写LinkedHashMap中的removeEldestEntry方法,当LRU中元素多余6个时,
* 删除最不经常使用的元素
* @param eldest
* @return
* @see java.util.LinkedHashMap#removeEldestEntry(java.util.Map.Entry)
*/
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
if(size() > 6){
return true;
}
return false;
}
public static void main(String[] args) {
LRU<Character, Integer> lru = new LRU<Character, Integer>(
16, 0.75f, true);
String s = "abcdefghijkl";
for (int i = 0; i < s.length(); i++) {
lru.put(s.charAt(i), i);
}
System.out.println("LRU中key为h的Entry的值为: " + lru.get('h'));
System.out.println("LRU的大小 :" + lru.size());
System.out.println("LRU :" + lru);
}
}
总结
平时工作上不要仅仅会用,还是要多看源码才能了解的更深,例如本篇文章的实现,如果对源码不了解是不会有这种思路的。
另外再说一下
这四个方法保证新添加的元素永远放到最后
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
LinkedHashMap.Entry<K,V> t =
new LinkedHashMap.Entry<K,V>(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
linkNodeLast(p);
return p;
}
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
TreeNode<K,V> t = new TreeNode<K,V>(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
补充扩展
基于过期时间的LRU缓存map
package algorithm;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class LocalCache {
//默认的缓存容量
private static int DEFAULT_CAPACITY = 16;
//最大容量
private static int MAX_CAPACITY = 64;
private ReentrantLock lock = new ReentrantLock();
private static Map<String, CacheData> linkedHashMap = new LinkedHashMap<String, CacheData>(DEFAULT_CAPACITY, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
//缓存淘汰
return MAX_CAPACITY < linkedHashMap.size();
}
};
private ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
public void put(String key, String value, long expire) {
lock.lock();
try {
CacheData data = new CacheData();
data.setKey(key);
data.setValue(value);
data.setExpireTime(expire + System.currentTimeMillis());
linkedHashMap.put(key, data);
if (expire > 0) {
removeAfterExpireTime(key, expire);
}
} finally {
lock.unlock();
}
}
private void removeAfterExpireTime(String key, long expireTime) {
scheduledExecutorService.schedule(() -> {
System.out.println("过期后清除该键值对");
linkedHashMap.remove(key);
}, expireTime, TimeUnit.MILLISECONDS);
}
/**
* 过期删除
*/
public CacheData get(String key) {
lock.lock();
try {
return linkedHashMap.getOrDefault(key, new CacheData());
} finally {
lock.unlock();
}
}
private static class CacheLocal {
private static LocalCache cache = new LocalCache ();
}
public static LocalCache getInstance() {
return CacheLocal.cache;
}
private class CacheData {
private String key;
private String value;
/**
* 存活时间
*/
private long expireTime;
private String getValue() {
return value;
}
private void setValue(String value) {
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getExpireTime() {
return expireTime;
}
public void setExpireTime(long expireTime) {
this.expireTime = expireTime;
}
}
}