继承关系
继承图上,可以看到这个LinkedHashMap继承整个HashMap,获得HashMap的全部特征,和HashMap相比,除此之外增加的特性是维护节点间的链表关系。而为了实现LinkedHashMap,JDK大叔设计的方式是在HashMap里面放一些钩子函数,当HashMap完成一定的动作后,调用钩子函数做些事情。除此之外,就是重写父类的某些关键的方法。
存储结构
存储结构上来说,LinkedHashMap与HashMap一致,只不过其节点内新增了前后节点的引用:
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);
}
}
可以看到LinkedHashMap使用的Entry 继承自HashMap的Node,在此基础上维护before、after引用。
重要的字段
/**
* 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;
/**
* The iteration ordering method for this linked hash map: <tt>true</tt>
* for access-order, <tt>false</tt> for insertion-order.
*
* @serial
*/
final boolean accessOrder;
可以看到在LinkedHashMap中,增加了三个自动字段,分别是头结点和尾节点的实例引用。还有布尔值accessOrder来控制LinkedHashMap的行为
构造方法
public LinkedHashMap(int initialCapacity, float loadFactor) {
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;
}
可以看到,LinkedHashMap的构造器与HashMap几乎没有区别。仅有最后一个构造函数增加了accessOrder进行控制。当accessOrder为true的时候将会使用访问顺序维护链表,这意味着每当访问一个节点上的数据的时候都会被移到链表的尾部。当accessOrder为false的时候,链表顺序将会以插入顺序关联起来,这意味着这个行为总会发生在新节点插入的时候。
钩子方法实现
上文我们知道,LinkedHashMap的设计依赖于钩子方法,这个口子已经在HashMap内已经铺垫了。
节点创建相关方法
HashMap内存在两种节点,一个是普通链表节点,一个是树节点。在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;
}
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;
}
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;
}
}
可以看到,创建节点的时候,都会增加调用linkNodeLast方法的步骤,将插入的节点连接到链表的最后。除此之外,树化与反树化的场合下也需要进行链表维护,其中replacementTreeNode在树化的时候调用,replacementNode在反树化的时候调用。
// 将节点转换为链表节点
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<K,V>(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
// 将节点转换为树节点
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<K,V>(q.hash, q.key, q.value, next);
transferLinks(q, t);
return t;
}
节点操作后置钩子
节点的操作包括插入,访问,移除,在这几个行为内都会有钩子方法承担对子类的扩展能力。在父类HashMap中这几个方法都是空实现。
- afterNodeInsertion(boolean evict) 在节点插入之后做些什么,在HashMap中的putVal()方法中被调用
- afterNodeAccess(Node<K,V> e) 在节点访问之后被调用,主要在put()已经存在的元素或get()时被调用,如果accessOrder为true,调用这个方法把访问到的节点移动到双向链表的末尾。
- afterNodeRemoval(Node<K,V> e) 在节点被删除之后触发的方法
afterNodeInsertion源码如下,比较简单,就是对当前节点在链表内的处理。
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
// 当前场景下 if都为false,就是说,LinkedList默认不进行删除
// 如果evict 为true,并实现了 在某个条件(比方说容量大于10)下进行删除头节点(最老节点)那么就相当于实现了
// LRU缓存策略(last recently used)
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
// 调用删除节点之后将会间接调用 afterNodeRemoval方法
// afterNodeRemoval会进行维护链表
removeNode(hash(key), key, null, false, true);
}
}
// 可以进行覆盖进行判断什么时候进行删除最老节点
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false;
}
// 删除一个节点之后,将会维护链表结构
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;
}
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;
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;
}
}
afterNodeRemoval方法如下:
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;
}
序列化相关操作
LinkedHashMap做了自定义的序列化操作,方法如下:
- internalWriteEntries() 将会被writeObject调用,这属于序列化的部分
- reinitialize() 将当前状态赋值为初始化状态,将会被clone方法或readObject方法调用
void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
s.writeObject(e.key);
s.writeObject(e.value);
}
}
void reinitialize() {
super.reinitialize();
head = tail = null;
}
// HashMap#reinitialize
void reinitialize() {
table = null;
entrySet = null;
keySet = null;
values = null;
modCount = 0;
threshold = 0;
size = 0;
}
总结
LinkedHashMap继承自HashMap,其内部增加链表头结点与尾节点,来维护节点的顺序性。在HashMap中存在大量钩子方法提供给LinkedHashMap进行继承并调整了他的行为。