简介
为什么我们需要LinkedHashMap:
相比于HashMap:遍历HashMap的顺序并不是HashMap放置的顺序,因为放置值是根据hash值确定,也就是无序。而LinkedHashMap,它虽然增加了时间和空间上的开销,构造了一个双向链表,但是LinkedHashMap保证了元素迭代的顺序。
该迭代顺序有两种,可以是插入顺序或者是访问顺序。LinkedHashMap继承了HashMap类,有着HashMap的所有功能(比如LinkedHashMap没有重写put,remove等方法,想加入元素直接调用父类的put,remove方法),还提供了记住元素添加顺序的方法。
所以简单来说:
1.LinkedHashMap 是在 HashMap 的基础之上,构建了一个双向链表。用于记录插入顺序或访问顺序
2.根据它的特性还可以实现简单的LRU策略等
如下图:
继承体系
其实看名字就可以知道LinkedHashMap实际底层就是Hashmap,只不过在底层多维护了一个双向链表
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
存储结点Entry
可以看见底层结点就是调用了父类的Node,只不过多加了一个before前驱结点,after后继结点
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);
}
}
成员变量
- head:双链表头
- tail:双链表尾
- accessOrder:顺序访问
//双链表的头(最大的)。
transient LinkedHashMap.Entry<K,V> head;
//双链表的尾部(最年轻的)。
transient LinkedHashMap.Entry<K,V> tail;
//boolean型,判断是否按访问顺序排序链表
//默认accessOrder为false,即迭代时的输出顺序就是插入顺序。
//比如插入1、2、3,迭代输出就是1、2、3;若为true,输出顺序即节点访问顺序。比如插入1、2、3,迭代之前访问了2,又访问了1,迭代出来就是3、2、1,
//每一次访问之后就将该访问的结点放入双向链表最后
final boolean accessOrder;
构造方法
5个构造方法
public LinkedHashMap(int initialCapacity, float loadFactor) {
//super调用父类方法,实际上就是hashmap传入初始容量,和负载因子
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
//默认构造方法
public LinkedHashMap() {
super();
accessOrder = false;
}
//传入map集合
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
//可以传入accessOrder,accessOrder的意思上面变量那里说明过这里不多说
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
常用方法
get()
实际上就是调用getNode方法传入key的hash值和key值,如果=null则返回null,否则调用afterNodeAccess(就是将访问过的结点加到双链表的末尾),并且返回e的值
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;
}
接下来看看getNode()和afterNodeAccess()方法
getNode()
这里getNode方法实际访问的就是Hashmap中的getNode,如果存在则返回该结点,不存在则返回null
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
afterNodeAccess()
这个方法实际上就是将访问了的结点给拿到最后,它的前驱结点和后继结点相连
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;
//如果b==null则说明p是头结点,否则将b指向a也就是 p.before=p.after
if (b == null)
head = a;
else
b.after = a;
//判断是否是尾结点,然后同上
if (a != null)
a.before = b;
else
last = b;
//如果尾结点为null,说明无结点,p就是头点
if (last == null)
head = p;
else {
//否则将尾结点指向p
p.before = last;
last.after = p;
}
//然后p变成tail
tail = p;
++modCount;
}
}
put()
LinkedHashMap本身没有重写父类的put方法,而是直接使用父类的put方法
这里是HashMap中Putval,不懂的可以看看
HashMap
可以看出put方法调用的父类的put
如果看过HashMap中putVal源码的朋友肯定知道其中有一个newNode方法,这里LinkedHashMap对其重写
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
linkNodeLast:
加入结点之后需要把他放入双向链表的尾部
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;
}
}
remove()
实际上LinkedHashMap删除结点也是直接使用的父类的remove如下图:
而LinkedhashMap主要重写的是afterNodeRemoval方法
看得出来其实就是把双向链表中的指针去掉
//LinkedHashMap
void afterNodeRemoval(Node<K,V> e) { // unlink
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;
}