HashMap和双向链表合二为一即是LinkedHashMap。所谓LinkedHashMap,其落脚点在HashMap,同HashMap不同的是,LinkedHashMap维护了一个Entry的双向链表,保证了插入的Entry中的顺序,这也是Linked的含义。

LinkedHashMap有序,可分为插入顺序和访问顺序两种。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)

为了实现双向链表,LinkedHashMap中提供了如下的Entry:

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

主要元素

  1. /**
  2. * 头指针,指向第一个node
  3. */
  4. transient LinkedHashMap.Entry<K,V> head;
  5. /**
  6. * 尾指针,指向最后一个node
  7. */
  8. transient LinkedHashMap.Entry<K,V> tail;
  9. /**
  10. * 一个条件变量,它控制了是否在get操作后需要将新的get的节点重新放到链表的尾部
  11. * LinkedHashMap可以维持了插入的顺序,但是这个顺序不是不变的,可以被get操作打乱。
  12. *
  13. * @serial
  14. */
  15. final boolean accessOrder;

构造函数如果不明确传入accessOrder的话,默认都是按插入序的。

HashMap声明的三个方法

  1. // Callbacks to allow LinkedHashMap post-actions
  2. void afterNodeAccess(Node<K,V> p) { }
  3. void afterNodeInsertion(boolean evict) { }
  4. void afterNodeRemoval(Node<K,V> p) { }

声明了三个方法供LinkedHashMap实现,afterNodeRemoval,afterNodeInsertion,afterNodeAccess。这三个方法的主要作用是,在删除,插入,获取节点之后,对链表进行维护。简单来说,这三个方法中执行双向链表的操作:

afterNodeRemoval

顾名思义,在节点remove操作后进行调用:

  1. //在节点删除后,维护链表,传入删除的节点
  2. void afterNodeRemoval(Node<K,V> e) { // unlink
  3. //p指向待删除元素,b执行前驱,a执行后驱
  4. LinkedHashMap.Entry<K,V> p =
  5. (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  6. //这里执行双向链表删除p节点操作,很简单。
  7. p.before = p.after = null;
  8. if (b == null)
  9. head = a;
  10. else
  11. b.after = a;
  12. if (a == null)
  13. tail = b;
  14. else
  15. a.before = b;
  16. }

afterNodeAccess

  1. //在节点被访问后根据accessOrder判断是否需要调整链表顺序
  2. void afterNodeAccess(Node<K,V> e) { // move node to last
  3. LinkedHashMap.Entry<K,V> last;
  4. //如果accessOrder为false,什么都不做
  5. if (accessOrder && (last = tail) != e) {
  6. //p指向待删除元素,b执行前驱,a执行后驱
  7. LinkedHashMap.Entry<K,V> p =
  8. (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
  9. //这里执行双向链表删除操作
  10. p.after = null;
  11. if (b == null)
  12. head = a;
  13. else
  14. b.after = a;
  15. if (a != null)
  16. a.before = b;
  17. else
  18. last = b;
  19. //这里执行将p放到尾部
  20. if (last == null)
  21. head = p;
  22. else {
  23. p.before = last;
  24. last.after = p;
  25. }
  26. tail = p;
  27. //保证并发读安全。
  28. ++modCount;
  29. }
  30. }

afterNodeInsertion

这是一个很奇葩的方法,虽然名字是在插入之后进行的维护链表的操作,但是默认实际上这个什么都没做,看代码:

  1. void afterNodeInsertion(boolean evict) { // possibly remove eldest
  2. LinkedHashMap.Entry<K,V> first;
  3. //removeEldestEntry(first)默认返回false,所以afterNodeInsertion这个方法其实并不会执行
  4. if (evict && (first = head) != null && removeEldestEntry(first)) {
  5. K key = first.key;
  6. removeNode(hash(key), key, null, false, true);
  7. }
  8. }
  9. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
  10. return false;
  11. }

为什么不执行也可以呢,这个要到put操作的时候就能看出来了

那么removeEldestEntry这个方法有什么用呢,看名字可以知道是删除最久远的节点,也就是head节点,这个方法实际是给我们自己扩展的。默认是没有用的,用LinkedHashMap实现LRU的代码中将可以看到它的作用

怎么记录的插入节点?

HashMap put 方法里会有创建Node的过程,LinkedHashMap复写了相关方法,实现了记录,然后添加到链表末位

  1. Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
  2. LinkedHashMap.Entry<K,V> p =
  3. new LinkedHashMap.Entry<K,V>(hash, key, value, e);
  4. linkNodeLast(p);
  5. return p;
  6. }
  7. TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
  8. TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
  9. linkNodeLast(p);
  10. return p;
  11. }

get(Object key)

LinkedHashMap 重写了 get 方法,根据 accessOrder 实现访问记录

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