Java 集合源码解析系列:
- 拆行解码 Java 集合源码之总览
- 拆行解码 Java 集合源码之 Collection 的三大体系
- 拆行解码 Java 集合源码之迭代器
- 拆行解码 Java 集合源码之 ArrayList
- 拆行解码 Java 集合源码之 LinkedList
- 拆行解码 Java 集合源码之 HashMap
- 拆行解码 Java 集合源码之 Hashtable
- 拆行解码 Java 集合源码之 LinkedHashMap
- 拆行解码 Java 集合源码之 PriorityQueue
- 拆行解码 Java 集合源码之 ArrayDeque
特性
LinkedHashMap 继承自 HashMap,像是 put、get、扩容等操作都是沿袭自父类。
只是在此基础上,重写了对相关结点的钩子方法,去构建了结点的双向链表。
这样做的目的,就是为了维护可预测的结点遍历顺序。
- 这种遍历顺序默认是结点的插入顺序(当 key 已存在的情况下,put 的更新时不会改变原有的顺序。),可以设置为访问顺序。
- 可以以 Map 为基准构建,将 Map 中结点的原有顺序作为遍历顺序。
- 基于访问顺序,可以方便地实现 LRU 缓存。
- 初始容量和负载因子,同 HashMap。
- 对视图的操作不会影响原有数据结构的顺序。
- 可以重写
removeEldestEntry(Map.Entry)
来自定义插入新结点时,对旧节点的操作。
结点
static class Entry<K,V> extends HashMap.Node<K,V> {
// 注意:HashMap.TreeNode 是继承自该类,所以当 LinkedHashMap 树化时也是支持顺序的。
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
构造函数
public LinkedHashMap(int initialCapacity, float loadFactor) {
// 容量和负载因子,完全沿袭 HashMap
super(initialCapacity, loadFactor);
// 默认是插入顺序
accessOrder = false;
}
public LinkedHashMap(int initialCapacity) {
super(initialCapacity);
accessOrder = false;
}
public LinkedHashMap() {
super();
accessOrder = false;
}
public LinkedHashMap(Map<? extends K, ? extends V> m) {
super();
accessOrder = false;
putMapEntries(m, false);
}
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
// 指定是访问顺序或插入顺序
this.accessOrder = accessOrder;
}
钩子方法
在 HashMap 源码解析的文章曾经说到, HashMap 定义了三个钩子方法,由子类实现。子类通过这三个方法,在结点插入、访问、删除的过程中,维护子类添加的结点属性。
而在 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;
}
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
// evict:是否删除最旧的结点
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
// 删除结点, HashMap 的方法
removeNode(hash(key), key, null, false, true);
}
}
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;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
结点构造方法
从上面可以看到,afterNodeInsertion 定义的是结点插入之后,是否删除最旧的结点。
没有提及结点插入时,如何维系顺序。
这就需要子类重写 HashMap 的几个结点构造方法:newNode、replacementNode、newTreeNode、replacementTreeNode。
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<>(hash, key, value, e);
// 将新结点放在最后, 默认的插入顺序
linkNodeLast(p);
return p;
}
Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
LinkedHashMap.Entry<K,V> t =
new LinkedHashMap.Entry<>(q.hash, q.key, q.value, next);
// 替换结点, 将顺序属性也已迁移掉
transferLinks(q, t);
return t;
}
TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
TreeNode<K,V> p = new TreeNode<>(hash, key, value, next);
// 将新结点放在最后, 默认的插入顺序
linkNodeLast(p);
return p;
}
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
TreeNode<K,V> t = new TreeNode<>(q.hash, q.key, q.value, next);
// 替换结点, 将顺序属性也已迁移掉
transferLinks(q, t);
return t;
}
结点顺序维护方法
其实和父类差不多,多的就是两个维护顺序的方法。
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;
}
}
// apply src's links to dst
private void transferLinks(LinkedHashMap.Entry<K,V> src,
LinkedHashMap.Entry<K,V> dst) {
LinkedHashMap.Entry<K,V> b = dst.before = src.before;
LinkedHashMap.Entry<K,V> a = dst.after = src.after;
if (b == null)
head = dst;
else
b.after = dst;
if (a == null)
tail = dst;
else
a.before = dst;
}