1.数据结构
LinkedHashMap继承自HashMap,底层数据结构和HashMap的结构一样并且拥有HashMap的所有特性。不同的是,LinkedHashMap新增了"按照插入顺序访问"和"删除最少访问元素策略"。
源码
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;
//继承Node,为数组的每个元素增加了before和after属性
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);
}
}
/**
* 控制两种访问模式的字段,默认为false
* true表示按照访问顺序把经常访问的key放到队尾
* false表示按照插入顺序访问
*/
final boolean accessOrder;
}
源码解析
从上述Map新增的属性可以看到,LinkedHashMap的数据结构很像把LinkedList的元素换成HashMap的Node,也正是因为增加了这些结构,从而能把Map元素串联起来,形成一个链表,而链表就可以保证顺序,维护元素插入的顺序。
2.新增
LinkedHashMap通过newNode/newTreeNode方法进行节点新增,下面以newNode为例进行源码解析,具体源码如下。
源码
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
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;
}
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
//新增节点等于尾节点
tail = p;
//last为空,说明链表为空,首尾节点相等
if (last == null)
head = p;
//链表有数据,直接建立新增节点和上个尾节点之间的前后关系
else {
p.before = last;
last.after = p;
}
}
}
源码解析
- LinkedHashMap初始化时,accessOrder为false,就会按照插入顺序提供访问,插入方法使用的是父类HashMap的put方法,不过覆写了put方法,执行中调用的是newNode/newTreeNode和afterNodeAccess方法。
- LinkedHashMap通过新增头节点、尾节点,给每个节点增加before、after属性。每次新增,都把节点追加到尾节点,这样就可以保证新增节点是按照顺序插入到链表中的。
3.访问
LinkedHashMap主要通过迭代器进行访问,迭代器初始化的时候,默认从头节点开始访问,在迭代的过程中,不断访问当前节点的after节点即可,迭代器的具体源码如下。
源码
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
//初始化时默认从头节点开始访问
LinkedHashIterator() {
//头节点作为第一个访问的节点
next = head;
expectedModCount = modCount;
current = 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;
//通过链表的after结构,找到下一个迭代的节点
next = e.after;
return e;
}
}
}
源码解析
- LinkedHashMap只提供了单向访问,即按照插入的顺序从头到尾进行访问,不能像LinkedList那样进行双向访问。
- 在新增节点时,已经维护了元素之间的插入顺序了,所以在迭代访问时,只需不断访问当前节点的下一个节点即可。
4.访问最少删除策略
这种策略也叫做LRU(Least recently used,最近最少使用),就是经常访问的元素会被追加到队尾,这样不经常访问的数据自然就靠近队头,然后通过设置删除策略(如当Map元素个数大于某个阈值时),删除头结点,具体源码如下。
源码
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{
public V get(Object key) {
Node<K,V> e;
//调用HashMap的get方法
if ((e = getNode(hash(key), key)) == null)
return null;
//如果设置了LRU策略,就把当前key移动到队尾
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
//删除很少被访问的元素
void afterNodeInsertion(boolean evict) {
//得到元素头节点
LinkedHashMap.Entry<K,V> first;
//removeEldestEntry来控制删除策略,如果队列不为空,并且删除策略允许删除的情况下,删除头节点
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
//删除头节点
removeNode(hash(key), key, null, false, true);
}
}
}
源码解析
- 通过afterNodeAccess方法把当前访问节点移动到队尾,不仅仅是get方法,执行getOrDefault、compute、computeIfAbsent、computeIfPresent、merge方法时,也会这么做。不断把经常访问的节点移动到队尾后,那么靠近队头的节点,自然就是很少被访问的元素了。
- LinkedHashMap本身是没有实现put方法的,调用的是HashMap的put方法,但LinkedHashMap实现了put方法中的afterNodeInsertion方法,这个方法实现了删除操作。