LinkedHashMap
Collection 系列文章的总目录:
- Collection 体系的三个核心约定
- Sorted & Navigable
- Iterator & Iterable
- Java 中的数组
- ArrayList
- LinkedList
- HashMap
- LinkedHashMap
- TreeMap
- HashSet/LinkedHashSet/TreeSet
与
HashMap
相比,LinkedHashMap
是插入有序的,即它能保证元素的插入顺序。
原理:
节点加入了 before
和 after
指针(引用),用来指向上一个和下一个节点:
新类型的节点:
// 使用成员变量保存:记录顺序的链表的头和尾
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
// 可以看到增加了before和after指针
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
顺序:
LinkedHashMap
默认保存的是元素的插入顺序
- 可以通过构造传入
accessOrder
来指定保存的顺序- false:默认,即元素的
插入顺序
,替换已有元素不会影响它的顺序 - true:保存元素的
访问顺序
,即get
和put替换
已存在的元素都会更新元素的访问顺序
- false:默认,即元素的
添加元素:
HashMap 的 putVal:模板方法模式
LinkedHashMap
继承了HashMap
,HashMap 中定义了模板方法putVal
,声明成 final然后在里面调用了很多模板方法,这些模板方法 HashMap 自己实现了,子类也可以覆盖模板方法,从而提供不同的实现
在 LinkedHashMap 中就是通过覆盖这些方法来实现的,而不需要自己再写一个 putVal 方法
// 创建新类型的节点,并更新记录顺序的链表
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;
}
// 访问节点后,如果accessOrder为true,则更新记录顺序的链表
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;
}
}
// 可能会删除最旧的元素,取决于removeEldestEntry()方法的时候,默认返回false
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);
}
}
实现 LRU:
使用 LinkedHashMap 实现 LRU:
-
LRU
:Least Recently Used,最近最少使用。即将旧的、很久不访问的数据删除 -
由于 LinkedHashMap 会保存更新的顺序,所以可以用来实现 LRU
实现:
-
accessOrder
要设置为 true,因为访问元素之后,这个元素就刷新了。当然替换元素也会刷新 -
覆盖
removeEldestEntry()
方法
摘抄自: A LRU Cache in 10 Lines of Java
import java.util.LinkedHashMap;
import java.util.Map;
public LRUCache<K, V> extends LinkedHashMap<K, V> {
private int cacheSize;
public LRUCache(int cacheSize) {
super(16, 0.75, true);
this.cacheSize = cacheSize;
}
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() >= cacheSize;
}
}
缺点:
- 维护双链表或链表+红黑树的结构,性能有损失
- 不是线程安全的
所以实践中还是使用更加成熟的 LRU 方案