LinkedHashMap

LinkedHashMap是HashMap的子类,内部使用双链表进行顺序的维护,内部类Entry为HashMap的Node的子类,类图:

Entry

构造器

三参数构造器:

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和链表(或红黑树)。