1 简述
LinkedHashMap继承自HashMap,它的很多操作都基于HashMap实现,存储结构同样是数组+链表+红黑树(JDK1.8)。
同时,它里面维护着一个Entry的双向链表,保证了插入数据的顺序。
2 源码分析
这里主要分析核心的部分。
包括双向链表的节点结构Entry,维护双向链表的两个方法 afterNodeRemoval、afterNodeAccess,以及重写的get方法。
Entry
/**
* HashMap.Node subclass for normal LinkedHashMap entries.
*/
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);
}
}
/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMap.Entry<K,V> tail;
继承了HashMap的Node,并增加before、after前后指针。
创建头结点head,尾结点tail,记录信息。
afterNodeRemoval
LinkedHashMap的remove方法是继承自HashMap,同时自身实现了双向链表的维护。
// 在节点删除后,维护链表,传入删除的节点
void afterNodeRemoval(Node<K,V> e) {
// p指向待删除元素,b执行前驱,a执行后驱
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// 这里执行双向链表删除p节点操作
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
afterNodeAccess
在某个节点被访问后,根据accessOrder的值,看看是否需要调整数据到双向链表尾部。
accessOrder在后面会提及。
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;
}
}
get
LinkedHashMap的get方法是重写HashMap的。
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
这里的逻辑跟HashMap一样,通过key的hash值快速找到value。
如果accessOrder为true,就把节点挪到双向链表尾部。
小结
针对我们最常用的三个方法,get、put、remove,LinkedHashMap都是使用的HashMap那套逻辑。同时,为了维护自身的双向链表,LinkedHashMap实现了HashMap中的三个方法,
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }
这三个方法是HashMap专门留下用于扩展的,在数据被put、remove时都会经历其中的方法。
LinkedHashMap也是抓住这一点,实现了数据在被操作后,继续维护每一个节点的前后指针,保证双向链表的正确。
3 accessOrder
在看LinkedHashMap源码时,我们会看到这个布尔变量,默认为false。
这个值就相当于一个开关,
为false时,双向链表就按照正常的插入顺序进行排列。
当为true时,LinkedHashMap就会在节点被访问后(get、remove、put),将节点放在双向链表尾部,保证最近访问过的节点永远在节点尾部。
这个结构就可以用来实现LRU缓存。
如果数据量满了,就把头节点删除,头节点永远是最近的,最少被访问的节点。
下面是个demo,这个也是LeetCode上的146题!
class LRUCache extends LinkedHashMap {
private int capacity;
public LRUCache(int capacity) {
//accessOrder为true
super(capacity, 0.75F, true);
this.capacity = capacity;
}
public int get(int key) {
return (int)super.getOrDefault(key, -1);
}
public void put(int key, int value) {
super.put(key, value);
}
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > capacity; // 缓存数据满了,就删除最近的头节点
}
}