linkedHashMap是Map接口的一个实现类,主要用来存储(key,value)类型的数据,与hashMap的区别是linkedHashMap会对插入的元素的顺序进行维护,而hashMap是无序的。
本篇主要讨论一下linkedHashMap底层代码的实现。
LinkedHashMap的类定义
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
可以看到,LinkedHashMap继承了HashMap,所以LinkedHashMap与HashMap一致,也是基于数组+链表+红黑树实现的。
对于HashMap不熟悉的,可以先看一下 HashMap源码分析。
局部变量
//LinkedHashMap使用双向链表维护插入节点的顺序
//head表示链表的头部指针,指向链表中的第一个元素
transient LinkedHashMap.Entry<K,V> head;
//tail表示链表的尾部指针,指向链表中的最后一个元素
transient LinkedHashMap.Entry<K,V> tail;
//为false时,表示不要对修改或者查询过的节点进行顺序的维护,按照插入的顺序保持链表中节点的顺序
//为true时,表示如果对节点修改,需要将修改了的节点移动到链表的最尾部
final boolean accessOrder;
Entry实现
//LinkedHashMap中存储的节点为Entry,这里可以看出,该类继承了HashMap的Node类
static class Entry<K,V> extends HashMap.Node<K,V> {
//与HashMap的Node比较,Entry多了before和after两个局部变量,用来保存节点插入的顺序
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
说明:
LinkedHashMap中的Entry也是继承自HashMap的Node类,只是比HashMap中的Node类多了before、after两个局部变量,用来指向当前entry的前后节点,保存了插入节点的顺序。
LinkedHashMap的无参构造函数
public LinkedHashMap() {
super();
//将accessOrder设置为false,表示不要对修改的节点进行顺序的维护,按照插入的顺序保持链表中节点的顺序
accessOrder = false;
}
put(K key, V value)方法
//linkedHashMap调用的是父类HashMap的put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//HashMap的putVal方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
//这里的实现不同,LinkedHashMap重写了该方法
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//这里的实现不同,LinkedHashMap重写了该方法
//当最节点进行修改时,判断是否需要将节点移动到链表尾部
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
//该方法也会回调LinkedHashMap的实现,当无具体实现的功能
afterNodeInsertion(evict);
return null;
}
//LinkedHashMap重写了newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
//新建立一个节点p
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
//将新增的节点p添加到链表最尾部
linkNodeLast(p);
return p;
}
//将新添加的节点增加到linkedHashMap链表的结尾
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
//将tail赋予last局部变量
LinkedHashMap.Entry<K,V> last = tail;
//将新增的节点赋予tail,也就是将链表尾节点tail指向新增的节点
tail = p;
//last等于null,说明之前链表中没有元素
if (last == null)
//将链表头节点指向新增的节点
head = p;
else {
//到这来说明之前链表中是有数据的
//将新增的节点添加到链表最尾部,维护好before与after指针的关系
p.before = last;
last.after = p;
}
}
//将节点e移动到链表的最尾部
void afterNodeAccess(Node<K,V> e) {
LinkedHashMap.Entry<K,V> last;
//accessOrder默认为false,表示不要对修改的节点进行顺序的维护,按照插入的顺序保持链表中节点的顺序
if (accessOrder && (last = tail) != e) {
//执行移动逻辑,将节点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;
}
}
说明:
LinkedHashMap的put函数,回调了HashMap的put函数,其中有两处不同:
- newNode,创建新节点的方式调用LinkedHashMap自己的实现;
- 当节点已经存在,并且修改了节点的值时,回调afterNodeAccess实现,该方法中执行是否需要将修改了的节点移动到链表的最尾部;
get(Object key)方法实现
//根据key获取对应的值
public V get(Object key) {
Node<K,V> e;
//调用hashMap的getNode方法获取节点
if ((e = getNode(hash(key), key)) == null)
return null;
//判断是否需要对操作了的节点进行顺序的维护,也就是将操作了的节点移动到链表最尾部,默认是false
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
remove(Object key)方法的实现
该方法回调HashMap的remove方法的实现,主要有的不同是HashMap的remove会回调LinkedHashMap的afterNodeRemoval方法。
//从链表中删除节点e
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;
}
forEach方法实现
//该方法遍历链表中的每一个元素,传入一个函数式接口,也就是要回调的自定义函数
public void forEach(BiConsumer<? super K, ? super V> action) {
//如果回调函数为null,报空指针异常
if (action == null)
throw new NullPointerException();
//将modCount赋予mc,后面判断如果在遍历之后,两个值不相等,则报错
int mc = modCount;
//按照链表中的顺序,遍历已经插入的节信息,并回调传入的自定义函数
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.key, e.value);
//如果modCount 与 mc不相等,则报ConcurrentModificationException,表示在遍历数组的时候,不允许对数组进行修改操作
//在putVal,clear等修改操作的时候会对modCount执行加1的操作
if (modCount != mc)
throw new ConcurrentModificationException();
}
说明:
- 首先传入要对遍历节点操作的自定义函数
- 当对链表中的元素进行遍历时,会在遍历之前,将modCount赋予一个临时遍历mc,遍历完后,在对最新的modCount与mc进行比较;
- 这里的遍历方式是按照LinkedHashMap维护的链表顺序,遍历节点数据;
- 遍历完后,如果最新的modCount与mc不相等,则报ConcurrentModificationException异常,表示不允许在遍历数据期间,对HashMap中的数据进行修改操作,当执行putValu,clear,remove等修改数据操作的时候,会对modCount执行加1的操作。