LinkedHashMap
LinkedHashMap是HashMap的子类,内部使用双链表进行顺序的维护,内部类Entry为HashMap的Node的子类,类图:
构造器
三参数构造器:
1public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
2 super(initialCapacity, loadFactor);
3 this.accessOrder = accessOrder;
4}
如果accessOrder为true,那么表示将顺序记录为访问顺序,否则为插入顺序,默认为false。而正是这一参数并对removeEldestEntry方法进行覆盖便可以快速实现一个简单的LRU缓存。
put
put操作其实和父类HashMap采用相同的实现,在HashMap部分也提到了afterNodeAccess和afterNodeInsertion方法其实是空实现,而在LinkedHashMap中对其进行了实现,Linked的特性也是在这里进行了体现。
newNode
LinkedhHashMap对此方法进行了覆盖:
1Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
2 LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
3 linkNodeLast(p);
4 return p;
5}
linkNodeLast方法负责双链表的连接:
1private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
2 LinkedHashMap.Entry<K,V> last = tail;
3 tail = p;
4 if (last == null)
5 head = p;
6 else {
7 p.before = last;
8 last.after = p;
9 }
10}
afterNodeAccess
顾名思义,此方法应该在一次访问之后被调用,那么什么算是一次访问呢?LInkedHashMap对此做出了定义:
- put/putAll/putIfAbsent
- get/getOrDefault
- compute/computeIfAbsent/computeIfPresent
- merge
1void afterNodeAccess(Node<K,V> e) { // move node to last
2 LinkedHashMap.Entry<K,V> last;
3 if (accessOrder && (last = tail) != e) {
4 LinkedHashMap.Entry<K,V> p =
5 (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
6 p.after = null;
7 if (b == null)
8 head = a;
9 else
10 b.after = a;
11 if (a != null)
12 a.before = b;
13 else
14 last = b;
15 if (last == null)
16 head = p;
17 else {
18 p.before = last;
19 last.after = p;
20 }
21 tail = p;
22 ++modCount;
23 }
24}
双链表的节点位置交换过程。
afterNodeInsertion
1void afterNodeInsertion(boolean evict) { // possibly remove eldest
2 LinkedHashMap.Entry<K,V> first;
3 if (evict && (first = head) != null && removeEldestEntry(first)) {
4 K key = first.key;
5 removeNode(hash(key), key, null, false, true);
6 }
7}
evict参数如果为false,表示处于创建模式,在反序列化时才会处于创建模式,而反序列化时当然不能进行节点淘汰,这便是此参数的意义。
正如前面提到过的,removeEldestEntry是实现LRU的关键:
1protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
2 return false;
3}
即默认不进行移除,可以看出,传给removeEldestEntry方法的是每个bin的第一个元素,因为无论参数accessOrder为true与否,第一个必定是最老(最先被插入或最久未被使用)的。
get
1public V get(Object key) {
2 Node<K,V> e;
3 if ((e = getNode(hash(key), key)) == null)
4 return null;
5 if (accessOrder)
6 afterNodeAccess(e);
7 return e.value;
8}
HashMap的get加afterNodeAccess触发的过程。
性能
与HashMap相比,LinkedHashMap由于在插入是需要进行额外的双链表链接工作,所以在插入性能上必定不如HashMap,但在遍历时,LinkedHashMap的性能反而更高,因为只需遍历链表即可,而HashMap需要遍历bin和链表(或红黑树)。