1.整体架构
HashMap是无序的,TreeMap可以按照key进行排序,LinkedHashMap可以维护key插入顺序。
LinkedHashMap本身继承HashMap,所以拥有HashMap的全部特性,此外在其基础上提供了两大特性:
- 按照插入顺序进行访问
- 实现了最先删除最少访问的功能,目的是将很久未被访问的key自动删除
相比较HashMap,LinkedHashMap新增的属性有,
// 继承HashMap的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);
}
}
// 通过表头和表尾记录key插入顺序
// 链表头
transient LinkedHashMap.Entry<K,V> head;
// 链表尾
transient LinkedHashMap.Entry<K,V> tail;
// true时,按照访问顺序将经常访问的key放在队尾;false时按照插入顺序进行访问
final boolean accessOrder;
LinkedHashMap的结构很像是将LinkedList的每个元素与HashMap的Node相结合,从而把Map的元素串联起来得到一个链表。链表本身的顺序性维护了插入元素的顺序。
2.源码解析
按顺序新增元素
LinkedHashMap初始化过程中,AccessOrder参数默认为false,即会按照插入顺序提供访问。LinkedHashMap新增节点时使用的是HashMap的put方法,底层运行的也是HashMap的putVal方法。不过对父类putVal方法中调用的newNode方法、newTreeNode方法和afterNodeAccess方法进行类重写。
newNode方法和newTreeNode方法控制新增节点追加到链表的尾部,这样就保证了插入顺序。
newNode方法源码,
// 父类HashMap中newNode方法
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
// 子类重写的newNode方法
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;
}
// linkNodeLast是LinkedHashMap的方法,用于将节点放置到链表尾
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
// 如果last是null,表示链表是空链表。则head和tail都指向p
if (last == null)
head = p;
else { // 如果链表非空,将p连接到last后面,tail指向p即可
p.before = last;
last.after = p;
}
}
newTreeNode方法与newNode方法一致,区别仅仅是将new的对象改为,
TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
TreeNode类依旧是HashMap中的TreeNode类,之后newTreeNode方法中会调用linkNodeLast方法将TreeNode放置到当前链表尾部,即this.tail的后面。
按顺序访问元素
LinkedHashMap只提供了单向访问,即按照插入的key的顺序从头到尾对key、value和entry进行访问,而不能像LinkedList一样双向访问。
LinkedHashMap可以通过增强for循环和迭代器的方式对key进行顺序访问,其原理是底层实现的LinkedKeyIterator类、LinkedValueIterator类和LinkedEntryIterator类,以LinkedKeyIterator类为例,
final class LinkedKeyIterator extends LinkedHashIterator implements Iterator<K>
这三个类时LinkedHashMap的内部类,他们的父类LinkedHashIterator是一个定义在LinkedHashMap内部的抽象类。这个抽象类决定了LinkedHashMap能够按照key的插入顺序进行访问,
abstract class LinkedHashIterator {
LinkedHashMap.Entry<K,V> next;
LinkedHashMap.Entry<K,V> current;
int expectedModCount;
LinkedHashIterator() {
// 初始化时,next变量指向LinkedHashMap的head。
next = head;
expectedModCount = modCount;
current = null;
}
public final boolean hasNext() {
// 判断next是否指向null
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变量指向当前节点,更近next为当前节点的after节点
current = e;
next = e.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指向的当前节点
current = null;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
使用这个类,LinkedHashMap定义了三个子类,
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(); }
}
这三个子类统一调用LinkedHashIterator类的nextNode方法,按顺序得到插入的键值对。在这三个子类的基础上,又创建了相应的3个内部类,LinkedEntrySet、LinkedKeySet和LinkedValueSet类。这三个类用于对迭代器进行操作,分布被LinkedHashMap的entrySet、keySet和valueSet方法调用,创建相应迭代器对象。以LinkedEntrySet类为例,
// LinkedHashMap类entrySet方法,返回LinkedEntryIterator迭代器对象
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}
final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { LinkedHashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
// 创建LinkedEntryIterator类对象
return new LinkedEntryIterator();
}
public final boolean contains(Object o) {
......
}
}
迭代器的next操作,实际上就是调用LinkedEntryIterator、LinkedKeyIterator和LinkedValueIterator对象的next方法,该方法中调用父类的nextNode方法得到下一次迭代的节点对象,并依据需求返回entry、key或value。
删除最少访问
这种策略叫做LRU(least recently used),经常访问的元素会被追加到链表尾部,不经常访问的元素会随着链表的改变靠近链表头部。之后通过设置此策略对不常访问的元素进行删除,
@Test
public void lruTest() {
// 将AccessOrder设置为true
LinkedHashMap<integer, Integer> map = new LinkedHashMap<Integer, Integer>(4, 0.75, true) {
put(10, 10);
put(9, 9);
put(20, 20);
put(1, 1);
}
@override
// 复写删除策略,设定当节点数大于3时,开始删除节点
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > 3;
}
};
log.info("初始化:{}",JSON.toJSONString(map));
Assert.assertNotNull(map.get(9));
log.info("map.get(9):{}",JSON.toJSONString(map));
Assert.assertNotNull(map.get(20));
log.info("map.get(20):{}",JSON.toJSONString(map));
运行结果,
初始化:{9:9,20:20,1:1}
map.get(9):{20:20,1:1,9:9}
map.get(20):{1:1,9:9,20:20}
当大于3个节点时,最先被添加的头节点(10, 10)被删除,之后对(9, 9)和(20, 20)进行访问,被访问过的节点自动移动至队尾。
转移到队尾的策略
LinkedHashMap中的get方法如下,
public V get(Object key) {
Node<K,V> e;
// 父类HashMap的getNode方法
if ((e = getNode(hash(key), key)) == null)
return null;
// 当设置AccessOrder为true时,调用该方法将节点移至队尾
if (accessOrder)
afterNodeAccess(e);
return e.value;
}
// 该方法的过程就是对链表的操作,将本次访问的节点移动至tail后面生成新的tail
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;
}
}
删除策略
LinkedHashMap添加节点调用的就是HashMap的put方法,该方法会调用HashMap中定义的putVal方法,putVal方法的最后有这样一行代码,
在HashMap中,afterNodeInsertion是空方法,没有代码。而LinkedHashMap对该方法进行了复写,
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
// 得到头节点,removeEldestEntry 控制删除策略,如果队列不为空,并且删除策略允许删除的情况下,返回true
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
// HashMap中删除节点的方法
removeNode(hash(key), key, null, false, true);
}
}
- 获取链表头部节点
- 首先会通过removeEldestEntry方法判断是否满足删除节点的条件
- 如果满足,则调用父类的删除节点的removeNode方法对节点进行删除
总结
LinkedHashMap的两个特性,在不影响HashMap查找的情况下,做到按顺序访问和LRU删除,只是简单地对HashMap进行了拓展。