顾名思义,LinkedHashMap是基于链表实现的有序的HashMap。LinkedHashMap内部维护了一个双向链表,链表定义了其元素迭代的顺序,通常的顺序就是元素被插入到LinkedHashMap的顺序。
LinkedHashMap摒弃了HashMap的乱序问题,同时也不像TreeMap一样有较高的开销。LinkedHashMap可以用于生成和原始Map顺序一样的拷贝,它提供了一个可以传入任意非空Map的构造函数。
LinkedHashMap提供两种迭代顺序:最后访问顺序和元素插入顺序(由LinkedHashMap内部属性accessOrder
决定,为true
时,迭代顺序为最后访问顺序,否则为元素插入顺序,默认为fasle
)。
LinkedHashMap实例调用put()
, putIfAbsent()
, get()
, getOrDefault()
, compute()
, computeIfAbsent()
, computeIfPresent()
或 merge()
方法将导致访问相应的元素。 putAll()
方法为指定Map中的每个KV对生成一次访问,访问顺序就是指定Map的原始的迭代器迭代顺序。除了上述方法,其他方法不会生成访问。
LinkedHashMap实现了Map接口的全部操作,允许null,由于需要维持链表顺序,其基本操作的性能相对HashMap稍差。并且如果定义了最后访问顺序排序的LinkedHashMap,其get()
操作会对其结构作出修改。此外,LinkedHashMap是线程不安全的。
由于LinkedHashMap继承了HashMap,我们这里仅讲述一下不同的地方,相同的操作本文将略过。
构造函数
LinkedHashMap的Entry继承自HasHMap.Node<K,V>
,在Entry中增加了到前后节点的reference,用来记录LinkedHashMap实例的迭代顺序。并且记录了双向链表的head和tail,用来按顺序迭代其元素。
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);
}
}
除了HashMap指定的三个构造函数,LinkedHashMap提供了第四个构造函数,他可以用来指定LinkedHashMap的迭代顺序是按照访问顺序还是插入顺序。
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
不指定accessOrder
时,默认其值为false
,即按照插入顺序迭代。下面两节分别讲述一下插入元素和访问元素是的链表操作:
插入元素
LinkedHashMap覆盖了HashMap的newNode()
方法,增加了将新节点link到双向链表的尾部的操作:
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
这个操作完成了元素插入顺序的记录。
同样,删除某个KV对时,LinkedHashMap覆盖的方法afterNodeRemoval(node)
会在删除节点的removeNode()
后调用,这个操作会生出迭代顺序链表中的相应节点。
void afterNodeRemoval(Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.before = p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a == null)
tail = b;
else
a.before = b;
}
此外插入一个映射后,如果这个Map被定义为创建模式(evict
为false
),而且要求删除年龄最大的映射时,需要将将链表的头部节点删除。不过当前的实现里,evict
始终为true
,而且removeEldestEntry()
始终返回fasle
,即jdk的LinkedHashMap和HashMap实现中,Map实例始终都不会是创建模式而去不删除年龄最老的映射。
void afterNodeInsertion(boolean evict) {
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
访问元素
访问某个节点后,会回调afterNodeAccess()
方法,将访问的节点移动到链表的尾部,访问某个节点的操作包括:put()
,replace()
,computeIfAbsent()
,computeIfPresent()
,compute()
,merge()
,以及get()
,getOrDefault()
。
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;
}
}