本文若无特殊说明,源码均为JDK1.8
概述
LinkedHashMap它继承自HashMap,实现了Map<K,V>接口。其内部还维护了一个双向链表,在每次插入数据,或者访问、修改数据时,会增加节点、或调整链表的节点顺序,以决定迭代时输出的顺序。默认情况下遍历的顺序是按照插入节点的顺序,这也是与HashMap最大的区别,也可以在构造时传入accessOrder参数,使得其遍历顺序按照访问的顺序输出(后面介绍)
因继承自HashMap,所以HashMap上文分析的特点,除了输出无序,其他LinkedHashMap都有,比如扩容的策略,哈希桶长度一定是2的N次方等等。
LinkedHashMap在实现时,就是重写override了几个方法。以满足其输出序列有序的需求。
源码分析
属性
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>{
/**
* Entry<K,V>继承自HashMap.Node<K,V>,在其基础上扩展了一下。改成了一个双向链表
* 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;
/**
* 默认是false,则迭代时输出的顺序是插入节点的顺序。若为true,则输出的顺序是按照访问节点的顺序。
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
final boolean accessOrder;
}
构造函数
/**
* 空的构造函数,accessOrder默认为false
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the default initial capacity (16) and load factor (0.75).
*/
public LinkedHashMap() {
super();
accessOrder = false;
}
/**
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the specified initial capacity and a default load factor (0.75).
* 指定具体的初始容量,加载因子默认为0.75
* @param initialCapacity the initial capacity
* @throws IllegalArgumentException if the initial capacity is negative
*/
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
/**
* Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
* with the specified initial capacity and load factor.
* 指定具体的初始容量,加载因子
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
/**
* 指定具体的初始容量,加载因子和迭代输出节点的顺序
* Constructs an empty <tt>LinkedHashMap</tt> instance with the
* specified initial capacity, load factor and ordering mode.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @param accessOrder the ordering mode - <tt>true</tt> for
* access-order, <tt>false</tt> for insertion-order
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
/**
* 利用另一个Map 来构建,
* 默认加载因子(0.75)和初始容量
* Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with
* the same mappings as the specified map. The <tt>LinkedHashMap</tt>
* instance is created with a default load factor (0.75) and an initial
* capacity sufficient to hold the mappings in the specified map.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
构造函数和HashMap相比,就是增加了一个accessOrder参数。用于控制迭代时的节点顺序。
新增
LinkedHashMap并没有重写任何put方法。但是其重写了构建新节点的newNode()方法.
newNode()会在HashMap的putVal()方法里被调用,putVal()方法会在批量插入数据putMapEntries(Map<? extends K, ? extends V> m, boolean evict)或者插入单个数据public V put(K key, V value)时被调用。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
// 构建新节点时,构建的是LinkedHashMap.Entry<K,V>,不再是Node
LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
// 将新增的节点,连接在链表的尾部
// link at the end of list
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;
}
}
删除
LinkedHashMap也没有重写remove()方法,因为它的删除逻辑和HashMap并无区别,但是他重写了afterNodeRemoval()方法,
// 删除节点e时,同步将e从双向链表删除
void afterNodeRemoval(Node<K,V> e) { // unlink
LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
// 待删除节点 p 的前置后置节点都置空
p.before = p.after = null;
// 如果前置节点是null,则现在的头结点应该是后置节点a
if (b == null)
head = a;
else
// 否则将前置节点b的后置节点指向a
b.after = a;
//如果后置节点时null ,则尾节点应是b
if (a == null)
tail = b;
else
//否则更新后置节点a的前置节点为b
a.before = b;
}
查找
LinkedHashMap重写了get()和getOrDefault()方法:
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
if (accessOrder)//相比HashMap中的get方法,这里是LinkedHashMap增加的
afterNodeAccess(e);
return e.value;
}
/**
* {@inheritDoc}
*/
public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return defaultValue;
if (accessOrder)////相比HashMap中的方法,这里是LinkedHashMap增加的
afterNodeAccess(e);
return e.value;
}
来看看afterNodeAccess()方法,这个方法的主要作用会将当前被访问到的节点e,移动双向链表的尾部,其源码如下:
void afterNodeAccess(Node<K,V> e) { // move node to last
// 原尾节点
LinkedHashMap.Entry<K,V> last;
//如果accessOrder 是true ,且原尾节点不等于e
if (accessOrder && (last = tail) != e) {
//节点e转成双向链表节点p
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//p现在是尾节点, 后置节点一定是null
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;
}
}
值得注意的是,afterNodeAccess()函数中,会修改modCount,因此当你正在accessOrder=true的模式下,迭代LinkedHashMap时,如果同时查询访问数据,也会导致fail-fast,因为迭代的顺序已经改变。
containsValue方法
重写了该方法,相比HashMap的实现,更为高效。
/**
* Returns <tt>true</tt> if this map maps one or more keys to the
* specified value.
*
* @param value value whose presence in this map is to be tested
* @return <tt>true</tt> if this map maps one or more keys to the
* specified value
*/
public boolean containsValue(Object value) {
//遍历一遍链表,去比较有没有value相等的节点,并返回
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
V v = e.value;
if (v == value || (value != null && value.equals(v)))
return true;
}
return false;
}
Demo
在每次插入数据,或者访问、修改数据时,会增加节点、或调整链表的节点顺序。以决定迭代时输出的顺序。
public class LinkedHashMapDemo {
public static void main(String[] args) {
Map<String,String> linkedHashMap = new LinkedHashMap();
linkedHashMap.put("a", "val_a");
linkedHashMap.put("b", "val_b");
linkedHashMap.put("c", "val_c");
linkedHashMap.get("a");
Iterator<Map.Entry<String, String>> iterator = linkedHashMap.entrySet().iterator();
System.out.println("不设置accessOrder");
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
linkedHashMap = new LinkedHashMap(10,0.75f,true);
linkedHashMap.put("a", "val_a");
linkedHashMap.put("b", "val_b");
linkedHashMap.put("c", "val_c");
linkedHashMap.get("a");
linkedHashMap.get("b");
linkedHashMap.put("d", "val_d");
iterator = linkedHashMap.entrySet().iterator();
System.out.println("设置accessOrder为true");
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}