文章目录
源码环境: JDK 1.8
LinkedHashMap 是 HashMap 的子类,在 HashMap 的基础上,对于每一个出现的节点 Node e,用双向链表来连接。可以理解为 LinkedHashMap = HashMap + LinkedList。
下方为一个 LinkedHashMap,由 head 到 tail 的顺序是 table[0] -> a -> table[3] -> c ->b。
1 主要属性
accessOrder,如果是 false 表示插入顺序,如果是 true 表示访问顺序。
具体说来,插入的时候两种情况都会将新元素插入最后,而访问的时候只有 true 会将访问的元素放在最后,false 不做操作。
head 是最老的节点,新插入或新访问的总是放在 tail。
有三种操作,即 put/get/remove,这里的 put/get/remove 不是指具体的方法,而是做相应操作的多个方法的统称。比如 put 相关方法指的是 put/putOrDefault/putVal。
-
put 有两种情况:如果碰到同样的key (不管是否允许覆盖)相当于访问,否则相当于插入。
-
get 相当于访问(这类方法被重写)。
-
remove 相关方法只要执行了删除操作,则一定将这个节点从双向链表中删除。
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>
{
transient LinkedHashMap.Entry<K,V> head; //最旧的节点
transient LinkedHashMap.Entry<K,V> tail; //最新的节点
final boolean accessOrder;// true是访问顺序,false是插入顺序
...
}
从上向下依次是 Map.Entry,HashMap.Node,LinkedHashMap.Entry 和 HashMap.TreeNode。
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);
}
}
2 构造器
有5个构造器,除了第四个单独指定 accessOrder,其他的一律 accessOrder = false。具体由 HashMap 的构造器来实现。
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
3 钩子函数的实现
在 HashMap 中,有三个方法 afterNodeAccess,afterNodeRemoval,afterNodeInsertion 未实现,在 LinkedHashMap 中实现了这三个方法。
afterNodeAccess,afterNodeRemoval 是对双链表的节点进行操作,可以按照我在 LinkedList 中提到的原则来考虑:
通常先假设用到的节点都不是 null,写出整个处理过程。
由于 null 没法使用 after/before,在使用 node.after/node.before 时要判断一下 node 是不是 null, 是 null 则对应一些特殊情况,一般会重置头结点或尾结点。
在 LinkedList 中我画了一些图,可以辅助理解相关的操作。可以看看 LinkedList 源码解析。
3.1 afterNodeAccess
为方便考虑,假定提到的节点都不重合,则有 5 个节点
<- head <-> b <-> p <-> a <-> last ->
1. 首先记
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
2. 将 p 移动到最后,并与 last 连接
last.after = p;
tail = p;// 将 p 置为新的 tail
p.after = null;
p.before = last;
3. 将 a 和 b 连接起来
b.after = a;
a.before = b;
4.由于 last 和 e 不为 null,所以只在 a/b 为 null 时考虑一下。
如果 a 为 null,则 e 是 tail,这种情况不存在。
如果 b 为 null,则 e 是 head,所以移动后 a 是 head。
源码
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;
if (b == null)
head = a;
else
b.after = a;
// 这个判断没有意义,因为 e 不是 tail
if (a != null)
a.before = b;
else
last = b;
// 这个判断没有意义,因为访问e,e 不为 null。
// 则整个Map至少有一个节点,不可能last为null
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
3.2 afterNodeRemoval
<- head <-> a <-> p <-> b <-> last ->
1. 首先记
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
2. 将 p 与 a 和 b 的连接断开
p.before = null;
p.after = null;
3. 将 a 和 b 连起来
b.after = a;
a.before = b;
4.由于 e 不为 null,所以只在 a/b 为 null 时考虑一下。
如果 b 为 null,则 e 是 head,所以移动后 a 是 head。
如果 a 为 null,则 e 是 tail,所以移动后 b 是 tail。
源码
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;
}
3.3 afterNodeInsertion
这个方法是在插入后对 head 进行处理,如果满足条件(即 evict 为 true,存在至少一个元素,且允许删除第一个元素),则删除 head 所在的节点。
而按照最开始的说法,无论 accessOrder 怎么样,总是会将新的节点插入到最后,这个方法里面并没有做。
根据 debug 的结果,将新节点插入到最后的操作是在 newNode 和 newTreeNode 方法中,具体调用了 linkNodeLast 来实现新节点插入最后的。具体看第 6 节。
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);
}
}
4 removeEldestEntry
默认删除最旧的(最前面) 的方法总是返回 false,即不删除,如果要实现删除功能,可以自己重写。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
有一个类继承了 LinkedHashMap,使其满足 LRU 性质,最大元素为 MAX_CAPACITY,可如下实现。
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return this.size() > this.MAX_CAPACITY ;
}
5 get
接下来看一下 get/put/remove 具体怎么实现链表的处理的。
重写了 get 和 getOrDefault 方法,在 HashMap 的基础上添加了判断 accessOrder 执行 afterNodeAccess 的操作。
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;
}
public V getOrDefault(Object key, V defaultValue) {
Node<K,V> e;
if ((e = getNode(hash(key), key)) == null)
return defaultValue;
if (accessOrder)//新添加
afterNodeAccess(e);//
return e.value;
}
6 put 相关
6.1 putVal
putVal 比较复杂,省略掉无关的东西,只看 LinkedHashMap 中有用的部分。
- 在第二个 if,如果当前桶是 null 会新建一个 Node。
- 如果当前节点是红黑树,putTreeVal 方法内部会新建一个 TreeNode 节点。
- 在对链表进行遍历的时候,创建新 Node。
- 如果找到了相同 key 的节点,此时 e != null,则不论是否替换,都会执行 afterNodeAccess,相当于访问了 e。
- 如果没找到相同的 key,则说明是 1 或 2 两种情况,此时执行 afterNodeInsertion。
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 ...
// 当前桶的位置为null
if ((p = tab[i = (n - 1) & hash]) == null)
// 创建新节点
tab[i] = newNode(hash, key, value, null);
else {
...
if ...
//如果是红黑树
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 (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 访问后处理
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
// 插入后处理
afterNodeInsertion(evict);
return null;
}
6.2 linkNodeLast
前面说过,afterNodeInsertion 并不管理将新的节点插入到链表最后。其实,在 LinkedHashMap 中,重写了 newNode 和 newTreeNode 方法,产生新的 Entry 或者 TreeNode 后,调用 linkNodeLast 将节点 p 插入到整个链表的最后。
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;
}
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;
}
// 将p放在tail后面,并重置tail
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;
}
}
7 remove
注意,在满足删除条件的情况下,先删除,然后调用 afterNodeRemoval 将对应 node 从链表中去掉。
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab; Node<K,V> p; int n, index;
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
if ...
else if ...
// 如果满足删除条件
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;
// 在删除node后,将其也从链表中删除。
afterNodeRemoval(node);
return node;
}
}
return null;
}
8 迭代器
使用的顺序是双链表不断 after 的顺序。
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator() {
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
return next != null;
}
final LinkedHashMap.Entry<K,V> nextNode() {
LinkedHashMap.Entry<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
current = e;
next = e.after;//下一个元素使用 after。
return e;
}
public final void remove() {
Node<K,V> p = current;
if (p == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
final class LinkedKeyIterator extends LinkedHashIterator
implements Iterator<K> {
public final K next() { return nextNode().getKey(); }
}
final class LinkedValueIterator extends LinkedHashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
final class LinkedEntryIterator extends LinkedHashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
下面是我的公众号,Java与大数据进阶,分享 Java 与大数据笔面试干货,欢迎关注