LinkedHashMap继承
自HashMap。和HashMap不同的是,它维护一条双向链表
,解决了遍历顺序和插入顺序一致的问题。并且还对访问顺序提供了相应的支持。因为LinkedHashMap的很多实现是基于HashMap实现的,所以如果要读懂LinkedHashMap还是需要先了解HashMap。可以参考我的这篇文章
/**
* 指向双向链表的头结点(最老的结点)
* 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;
/**
* LinkedHashMap的迭代排序方法,如果是true,是进入顺序,如果为false,为插入顺序
* The iteration ordering method for this linked hash map: true
* for access-order, false for insertion-order.
*
* @serial
*/
final boolean accessOrder;
/**
* Constructs an empty insertion-ordered LinkedHashMap instance
* with the default initial capacity (16) and load factor (0.75).
*/
public LinkedHashMap() {
super();
accessOrder = false;
}
/**
* Constructs an empty insertion-ordered LinkedHashMap instance
* with the specified initial capacity and a default load factor (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 LinkedHashMap 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;
}
首先是LinkedHashMap的插入过程。使用put方法,LinkedHashMap本身并没有重写自己继承的put方法,它用的还是HashMap中的put方法,那究竟是怎么做到不重写put方法就保证了插入的顺序了呢?
LinkedHashMap.Entry
/**
* HashMap.Node的子类
* 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);
}
}
原本HashMap的Node结点肯定不会记录插入顺序
,但是上面的Entry结点就不一样了,它可以记录自己的上一个结点和下一个结点,记录插入的顺序。那这个结点是怎么添加进去的呢?那就不得不说另一个重要的方法了。LinkedHashMap通过重写HashMap的newNode方法
,在HashMap添加结点时调用了子类LinkedHashMap的newNode方法
,实现Entry结点的插入。这样既添加了双向链表的需求又没有破坏原本HashMap原本的方法。
LinkedHashMap#newNode
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;
}
LinkedHashMap#linkNodeLast
// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
//将尾结点复制给last
LinkedHashMap.Entry<K,V> last = tail;
//新添加的结点为尾结点
tail = p;
//如果是第一次添加,尾结点也是头结点
if (last == null)
head = p;
//否则
else {
//将上一个尾结点赋值给新添加结点的上一个结点,插入结点的前驱
p.before = last;
//上一个尾结点的后继是当前尾结点
last.after = p;
}
}
在HashMap中结点的类型有两种,一种是Node普通结点
,一种是TreeNode树节点
,Entry继承了HashMap.Node,可以无缝的加入,那么TreeNode呢?我们来看看HashMap中TreeNode结点的定义。
HashMap.TreeNode
/**
* Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
* extends Node) so can be used as extension of either regular or
* linked node.
*/
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
....
}
我们可以看到,HashMap中的TreeNode结点
居然继承了LinkedHashMap.Entry结点
,因为这个缘故,当然可以直接在LinkedHashMap中使用TreeNode了。
LinkedHashMap#newTreeNode
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
linkNodeLast(p);
return p;
}
补充
当使用HashMap的putVal方法时还会调用一个在LinkedHashMap中重要的操作afterNodeInsertion
HashMap#putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
....
++modCount;
if (++size > threshold)
resize();
//注意这个操作
afterNodeInsertion(evict);
return null;
}
LinkedHashMap#afterNodeInsertion
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
LinkedHashMap#removeEldestEntry
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
当我们基于 LinkedHashMap 实现缓存时,通过覆写removeEldestEntry
方法可以实现自定义策略的 LRU 缓存。
LinkedHashMap结点的删除操作同样的是使用父类HashMap内的相关方法(remove,removeNode),而没有重写相关方法。那该方法又怎么保持删除结点后链表的维护呢?让我们来看相关代码。
HashMap#remove
public V remove(Object key) {
Node<K,V> e;
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
HashMap#removeNode
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
//p表示头结点或者将要被删除的结点的上一个结点
Node<K,V>[] tab; Node<K,V> p; int n, index;
//如果table不为空,长度>0,table中hash对应下标的第一个结点不为null,有值
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
//node指将要被删除的结点
Node<K,V> node = null, e; K k; V v;
//与上面相同
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p;
//链表头结点不等,且后面还有结点
else if ((e = p.next) != null) {
//树节点
if (p instanceof TreeNode)
node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
else {
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) {
//赋值给node
node = e;
break;
}
//赋值给p,方便后面操作
p = e;
} while ((e = e.next) != null);
}
}
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
//如果是树节点
if (node instanceof TreeNode)
((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
//如果头结点是将要被删除的结点
else if (node == p)
tab[index] = node.next;
else
p.next = node.next;
++modCount;
--size;
//删除结点后维护链表的关键操作!!!!!!
afterNodeRemoval(node);
return node;
}
}
return null;
}
删除结点后关键的操作是afterNodeRemoval
方法,对于HashMap来说,该方法未做任何操作
。LinkedHashMap重写了该方法,实现了删除结点后链表的维护操作。
LinkedHashMap#afterNodeRemoval
void afterNodeRemoval(Node<K,V> e) { // unlink
//复制删除结点e,并记录删除结点的上一个结点和下一个结点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//将删除结点指向前结点和后结点的引用清空
p.before = p.after = null;
//如果删除结点的前一个结点为空,表明是第一个结点,改变head指向删除结点的下一个结点
if (b == null)
head = a;
//否则前面存在结点,(删除节点的上一个结点的after)指向(删除结点的下一个结点)
else
b.after = a;
//如果删除结点为尾结点,改变tail指向删除结点的上一个结点
if (a == null)
tail = b;
//否则改变(删除结点的下一个结点的before)指向(删除结点的上一个结点)
else
a.before = b;
}
LinkedHashMap#get
/**
* 返回指定键的值
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
* 如果map中保存着相应的键,返回相应的值,否则返回null
* More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key==null ? k==null :
* key.equals(k))}, then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
* //返回null不一定代表不存在键
*
A return value of {@code null} does not necessarily
* indicate that the map contains no mapping for the key; it's also
* possible that the map explicitly maps the key to {@code null}.
* The {@link #containsKey containsKey} operation may be used to
* distinguish these two cases.
*/
public V get(Object key) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return null;
//如果为true,将结点移动到链表末尾
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
LinkedHashMap#afterNodeAccess
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMap.Entry<K,V> last;
//accessOrder=true并且访问的结点不是尾结点
if (accessOrder && (last = tail) != e) {
//复制访问结点e,并记录删除结点的上一个结点和下一个结点
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
//访问节点的after置空(变成尾结点)
p.after = null;
//如果当前结点e为头结点,更换头结点为下一个结点
if (b == null)
head = a;
//否则,当前结点的上一个结点的after指向当前结点的下一个结点
else
b.after = a;
//如果当前结点不是尾结点,当前结点的下一个结点的before指向当前节点的上一个结点
if (a != null)
a.before = b;
//否则,last指向当前结点的上一个结点(这里是为了适配非尾结点的状况,p.before = last)
else
last = b;
//如果既是头结点又是尾结点,头结点指向当前结点(上面头结点指向空)
if (last == null)
head = p;
//否则
else {
//当前结点的上一个结点是尾结点
p.before = last;
//尾结点的下一个结点是当前结点(至此将访问的当前结点移动到链表尾部)
last.after = p;
}
//尾结点指向当前结点
tail = p;
++modCount;
}
}
测试
Map<String, String> linkedHashMap = new LinkedHashMap<>(4, 0.75f, true);
linkedHashMap.put("test1", "fjx1");
linkedHashMap.put("test2", "fjx2");
linkedHashMap.put("test3", "fjx3");
System.out.println("开始时顺序:");
Set<Map.Entry<String, String>> set = linkedHashMap.entrySet();
set.forEach((entry)-> System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue()));
System.out.println("通过get方法,导致key为test1对应的Entry到表尾");
linkedHashMap.get("test1");
Set<Map.Entry<String, String>> set2 = linkedHashMap.entrySet();
set2.forEach((entry)-> System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue()));