前面我们有说LinkedHashMap
的顺序有两种,一种是插入顺序,这也是默认的顺序,因为LinkedHashMap
的内部维护着一个双链表,元素的插入每次只需插入到尾节点即可,遍历的时候,从头结点开始,就可按照插入顺序遍历到链表尾部;还有一种顺序是访问排序,设置访问顺序的标识是final boolean accessOrder
,设置此属性的值为true
即可按照访问的顺序对数据进行排序,每次访问一个元素之后,会将该元素置于链表的尾节点
put
我们首先来说put()
中涉及到的访问顺序的操作,在HashMap
的putVal()
中有这样的一段代码
HashMap#putVal():
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
可以看到,在添加元素的时候,发现该key值上有元素值,除了覆盖原始值之外,还调用了afterNodeAccess()
,而这个方法在HashMap
中是空实现,具体的实现在LinkedHashMap
中
LinkedHashMap#afterNodeAccess():
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<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
且新增节点e
不能为尾节点,正好符合HashMap
的处理,进入判断体,我们可以看到,首先获取到新增节点的前后节点指针引用(前指针引用下面简称为b,后指针引用下面简称a),将当前节点后一节点的指针设为空,
然后判断此节点的是不是头结点,如果是头结点则将此节点的后一节点a
设置为头结点,如果不是,则将b
的后一节点指针指向a
如果当前节点不是尾节点(这个是肯定的,前面判断过了)则将a
的前一节点指针指向b
,
上面两步将节点e
脱离了链表出来,下面则会将e
添加到链表头部
首先会判断下last
是否为空,last
的赋值在上面,默认为尾节点
并且不会为空,所以这里会走else,首先将当前节点的前一节点的指针指向原先的尾节点,然后将尾节点的下一节点指针指向当前节点,那么当前节点则变成了尾节点,最后将当前节点赋值给尾节点tail
。
如此便按照访问顺序修改了集合中链表的元素顺序。
get
LinkedHashMap#get():
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;
}
这里也是调用了afterNodeAccess
方法 不在多说