概述

LinkedHashMap是通过哈希表和链表实现的,它通过维护一个链表来保证对哈希表迭代时的有序性,而这个有序是指键值对插入的顺序。另外,当向哈希表中重复插入某个键的时候,不会影响到原来的有序性。

LinkedHashMap的实现主要分两部分,一部分是哈希表,另外一部分是链表。哈希表部分继承了HashMap,拥有了HashMap那一套高效的操作。
image.png
从图中可知,LinkedHashMap 是继承自 HashMap 的,所以它已经从 HashMap 那里继承了与哈希表相关的操作了

LinkedHashMap的属性

  1. static class Entry<K,V> extends HashMap.Node<K,V> {
  2. Entry<K,V> before, after;
  3. Entry(int hash, K key, V value, Node<K,V> next) {
  4. super(hash, key, value, next);
  5. }
  6. }
  7. private static final long serialVersionUID = 3801124242820219131L;
  8. /**
  9. * 双链接列表中的头(最年长的)。
  10. */
  11. transient LinkedHashMap.Entry<K,V> head;
  12. /**
  13. * 双链接列表的尾部(最年轻的)。
  14. */
  15. transient LinkedHashMap.Entry<K,V> tail;
  16. /**
  17. * 此链接哈希映射的迭代排序方法:访问顺序为true,插入顺序为false。
  18. * @serial
  19. */
  20. final boolean accessOrder;

可以发现:LinkedHashMap的链表节点继承了HashMap的节点,而且每个节点都包含了前指针和后指针,所以这里可以看出它是一个双向链表.
transient LinkedHashMap.Entry head; : 头指针
transient LinkedHashMap.Entry tail; : 尾指针

LinkedhashMap的一些方法

在HashMap源码中有这三个空的方法,其实这三个方法表示的是在访问、插入、删除某个节点之后,进行一些处理,它们在LinkedHashMap都有各自的实现。LinkedHashMap正是通过重写这三个方法来保证链表的插入、删除的有序性。

  1. _void _afterNodeAccess(Node e)
  2. _void _afterNodeInsertion(_boolean _evict)
  3. _void _afterNodeRemoval(Node e)

afterNodeAccess()

移动节点到尾部
就是把当前节点e移至链表的尾部。因为使用的是双向链表,所以在尾部插入可以以O(1)的时间复杂度来完成。并且只有当accessOrder设置为true时,才会执行这个操作。

  1. void afterNodeAccess(Node<K,V> e) { // move node to last
  2. LinkedHashMap.Entry<K,V> last;
  3. //当accessOrder的值为true,且e不是尾节点
  4. if (accessOrder && (last = tail) != e) {
  5. LinkedHashMap.Entry<K,V> p =
  6. (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  7. p.after = null;
  8. if (b == null)
  9. head = a;
  10. else
  11. b.after = a;
  12. if (a != null)
  13. a.before = b;
  14. else
  15. last = b;
  16. if (last == null)
  17. head = p;
  18. else {
  19. p.before = last;
  20. last.after = p;
  21. }
  22. tail = p;
  23. ++modCount;
  24. }
  25. }

afterNodeInsertion()

afterNodeInsertion方法是在哈希表中插入了一个新节点时调用的,它会把链表的头节点删除掉,删除的方式是通过调用HashMap的removeNode方法。

  1. void 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. }

afterNodeRemoval()

当HashMap删除一个键值对时调用的,它会把在HashMap中删除的那个键值对一并从链表中删除,保证了哈希表和链表的一致性。

  1. void afterNodeRemoval(Node<K,V> e) { // unlink
  2. LinkedHashMap.Entry<K,V> p =
  3. (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  4. p.before = p.after = null;
  5. if (b == null)
  6. head = a;
  7. else
  8. b.after = a;
  9. if (a == null)
  10. tail = b;
  11. else
  12. a.before = b;
  13. }

get()

get()方法,它调用的是HashMap的getNode方法来获取结果的

  1. public 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. }

结论

LinkedHashMap相对于HashMap,增加了双链表的结果(即节点中增加了前后指针),其他处理逻辑与HashMap一致,同样也没有锁保护,多线程使用存在风险。