学习无止境,探索无止境
1. LinkedHashMap简介
LinkedHashMap继承自HashMap,所以拥有HashMap的所有特性,在HashMap的基础上新增了双向链表结构,实现对数据按照插入顺序或访问顺序 LinkedHashMap继承关系:
- LinkedHashMap继承子HashMap并实现了Map接口
- 具备HashMap的所有特性,HashMap特性可以参考Android源码简单集合之HashMap
2. LinkedHashMap结构
LinkedHashMap在HashMap的基础上,新增了双向链表结构,由LinkedHashMapEntry表示LinkedHashMap的节点。
- LinkedHashMap中只有一条双向链表,包含map中所有元素
- 双向链表中的节点,通过记录前置节点和后置节点连接整个链表
- 新增的节点插入到双向链表尾部
- 双向链表支持插入顺序排序或访问顺序排序
3. LinkedHashMap源码
3.1. 成员属性
transient LinkedHashMapEntry<K,V> head; // 双向链表中的表头,旧数据靠近表头
transient LinkedHashMapEntry<K,V> tail; // 双向链表中的表尾,新插入数据放在表尾
final boolean accessOrder; // 是否按访问顺序排序,最近被使用的节点放在表尾
3.2. 内部节点
Entry继承自HashMap.Node节点,在它基础上新增了before和after,用于构建双向链表。
// LinkedHashMapEntry 继承自 HashMap.Node
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
// before: 前置节点
// after: 后置节点
LinkedHashMapEntry<K,V> before, after;
LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
双向链表结构如下图:
3.3. 构造方法
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;
}
- 前四个构造方法,accessOrder为false,双向链表按照元素的插入顺序排序。
- 最后一个构造方法支持设置accessOrder,accessOrder为true时,双向链表按照元素的访问顺序排序,最近被访问的元素放在表尾。
3.4. afterNodeRemoval方法
// HashMap源码removeNode节点方法,map中存在匹配的节点,则删除节点,最后调用afterNodeRemoval方法做节点删除后的操作,在LinkedHashMap中有用到
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
...
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
...
afterNodeRemoval(node);
return node;
}
}
return null;
}
// 删除双向链表中的节点
void afterNodeRemoval(Node<K,V> e) {
// 记录e节点为p
// b记作p节点的前置节点
// a记作p节点的后置节点
LinkedHashMapEntry<K,V> p = (LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
// 清理删除节点的前置节点和后置节点,方便GC
p.before = p.after = null;
// 如果b==null成立,说明删除的p节点为head节点,将a节点置为新的head结点;否则,将a节点置为b节点的after节点
if (b == null)
head = a;
else
b.after = a;
// 如果a==null成立,说明删除的p节点为tail节点,将a节点置为新的tail节点;否则,将b节点置为a节点的before节点
if (a == null)
tail = b;
else
a.before = b;
}
afterNodeRemoval方法主要用于删除双向链表结构中的元素,删除流程如下:
假设删除节点为entry2节点记作p节点,entry1节点为entry2节点的before记作b节点,entry3节点为entry2节点的after记作a节点。
- 如果b==null,则p节点是head结点,将a节点置为新的head结点
- 如果b!=null,则p节点不是head节点,将a节点置为b节点的after节点
- 如果a==null,则p节点是tail节点,将a节点置为新的tail节点
- 如果a!=null,则p节点不是tail节点,将b节点置为a节点的before节点
3.6. afterNodeInsertion方法
// HashMap源码putVal节点方法,map中新插入一个元素后,调用afterNodeInsertion方法,做节点插入后的操作,LinkedHashMap中用到
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
...
afterNodeInsertion(evict);
return null;
}
// map中插入新节点时的操作,主要用于LinkedHashMap中
void afterNodeInsertion(boolean evict) {
LinkedHashMapEntry<K,V> first;
// head节点不为null,removeEldestEntry返回true,则从双向链表删除head节点
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
afterNodeInsertion方法用于插入节点之后的操作,当removeEldestEntry返回true,删除head节点。
3.7. afterNodeAccess方法
// HashMap源码putVal节点方法,map中更新已存在的元素,调用afterNodeAccess方法,做节点更新后的操作,LinkedHashMap中用到
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
...
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 更新map中的元素之后
afterNodeAccess(e);
return oldValue;
}
}
...
}
// 更新map中节点或访问map中的节点时,将更新或访问的节点移动到双向链表的尾部,主要用于LinkedHashMap中
void afterNodeAccess(Node<K,V> e) { // move node to last
LinkedHashMapEntry<K,V> last;
// 按照访问顺序排序时 且 e不是在链表尾部
if (accessOrder && (last = tail) != e) {
// 记录e节点为p
// b记作p节点的前置节点
// a记作p节点的后置节点
LinkedHashMapEntry<K,V> p =
(LinkedHashMapEntry<K,V>)e, b = p.before, a = p.after;
// 将p节点的after节点置为null,因为p节点将成为tail节点
p.after = null;
// 如果b==null成立,说明p节点是head节点,将a节点置为新的head节点,否则,将b节点的after指向a节点
if (b == null)
head = a;
else
b.after = a;
// 如果 a!=null成立,说明p节点有后置节点,将a节点的before指向b节点;否则,将临时的last节点指向b节点
if (a != null)
a.before = b;
else
last = b;
// 如果last==null成立,说明p为head节点且p为tail节点,head节点指向p节点;否则,p的before节点指向last节点,last节点的after指向p节点
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
// tail节点指向p
tail = p;
++modCount;
}
}
afterNodeAccess方法主要用于更新双向链表结构中的顺序,更新流程如下:
假设更新节点为entry2节点,entry1节点为entry2节点的before记作b节点,entry3节点为entry2节点的after记作a节点。
- 如果b==null,则p节点是head节点,将a节点置为新的head节点
- 如果b!=null,则p节点不是head节点,将b节点的after指向a节点
- 如果a==null,则p节点是tail节点,将临时的last节点指向b节点
- 如果a!=null,则p节点不是tail节点,将a节点的before指向b节点
- 如果last==null
- 说明 b==null(即p是head节点)且 a==null(即p是tail节点)
- 上述条件成立,将p节点置为新的head节点
- 如果last!=null,则p的before节点需要指向last节点,last节点的after需要指向p节点
- 最后将tail节点指向p节点
3.8. get方法
// 如果map中有该节点,且双向链表按照访问顺序排序,则通过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;
}
结语: LinkedHashMap的主要操作已经分析完毕,LinkedHashMap的数据存储结构和HashMap一样,在其基础上增加了双向链表结构,通过accessOrder来控制对双向链表的排序,默认为插入顺序排序,当accessOrder为true时,按照访问顺序排序,在LruCache应用中采用了访问顺序排序。