LinkedHashMap的整体架构

LinkedHashMap 本身继承 HashMap,所以它拥有 HashMap 的所有特性,再此基础上,还提供了两大特性:

  • 按照插入 / 访问顺序进行访问
  • 实现了访问最少最先删除功能,其目的是:把很久都没有访问的 key 自动删除

LinkedHashMap 通过新增头节点、尾节点,给每个节点增加 before、after 属性,
每次 put() 新增时,都把节点追加到链表的尾部等手段,在新增时,就已经维护了按照插入顺序的链表结构了。
若属性 accessOrder == true ,则每次 get() 查找时,都会把查找到的节点移动到链表尾部,从而维护了按照访问顺序的链表结构。
LinkedHashMap 的数据结构很像是把 LinkedList 的每个元素换成了 HashMap 的 Node,像是两者的结合体,也正是因为增加了这些结构,从而能把 Map 的元素都串联起来,形成一个链表,而链表就可以保证顺序了。

  1. public class LinkedHashMap<K,V> extends HashMap<K,V>
  2. implements Map<K,V> {
  3. static class Entry<K,V> extends HashMap.Node<K,V> {
  4. Entry<K,V> before, after;
  5. Entry(int hash, K key, V value, Node<K,V> next) {
  6. super(hash, key, value, next);
  7. }
  8. }
  9. private static final long serialVersionUID = 3801124242820219131L;
  10. // 双向链表的头节点
  11. transient LinkedHashMap.Entry<K,V> head;
  12. // 双向链表的尾节点
  13. transient LinkedHashMap.Entry<K,V> tail;
  14. // 此 LinkedHashMap 的迭代排序方法
  15. // 值为 true 表示按照访问排序,false 表示按照插入顺序
  16. // 值默认 false
  17. final boolean accessOrder;
  18. // 构造一个空的按照插入顺序迭代的 LinkedHashMap 实例,具有指定的初始容量和负载因子
  19. public LinkedHashMap(int initialCapacity, float loadFactor) {
  20. super(initialCapacity, loadFactor);
  21. accessOrder = false;
  22. }
  23. // 构造一个空的按照插入顺序迭代的 LinkedHashMap 实例,具有指定的初始容量和默认负载因子(0.75)
  24. public LinkedHashMap(int initialCapacity) {
  25. super(initialCapacity);
  26. accessOrder = false;
  27. }
  28. // 构造一个空的按照插入顺序迭代的 LinkedHashMap 实例,具有默认的初始容量(16)和负载因子(0.75)
  29. public LinkedHashMap() {
  30. super();
  31. accessOrder = false;
  32. }
  33. // 构造一个按照插入顺序的 LinkedHashMap 实例,实例具有与指定 map 相同的映射
  34. // 实例使用默认的负载因子(0.75)和足够容纳指定 map 中的映射的初始容量
  35. public LinkedHashMap(Map<? extends K, ? extends V> m) {
  36. super();
  37. accessOrder = false;
  38. putMapEntries(m, false);
  39. }
  40. // 构造一个空的 LinkedHashMap 实例,该实例具有指定的初始容量、负载因子和排序模式
  41. public LinkedHashMap(int initialCapacity,
  42. float loadFactor,
  43. boolean accessOrder) {
  44. super(initialCapacity, loadFactor);
  45. this.accessOrder = accessOrder;
  46. }
  47. }

LinkedHashMap按照插入顺序(新增)

LinkedHashMap 只提供了单向访问,不能像LinkedList 那样双向访问。主要通过迭代器进行访问。
LinkedHashMap 初始化时,属性 accessOrder 默认为 false,就是会按照插入顺序提供访问。
LinkedHashMap 本身是没有 put() 实现的,调用的是父类 HashMap 的 put(),但 LinkedHashMap 重写了 putVal() 中的调用 newNode()、newTreeNode()、afterNodeAccess()、afterNodeInsertion() 方法

  • newNode() / newTreeNode() 增加逻辑:新增节点追加到链表尾部,保证可以按照插入顺序访问
  • afterNodeAccess():如果属性 accessOrder == true ,此方法将当前节点移动到链表尾部
  • afterNodeInsertion():如果属性 accessOrder == true ,此方法可能会删除头节点(满足一定条件)
    • 只有当 evict == true && 头节点不为 null && 重写的 removeEldestEntry() 返回 true 都满足时,才会删除头节点

HashMap 的笔记引用如下:
HashMap


LinkedHashMap 中重写的 newNode() 底层源码实现如下:

  1. // LinkedHashMap 中重写的 newNode()
  2. // 构造一个节点返回,并将该节点追加到链表的尾部
  3. Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
  4. LinkedHashMap.Entry<K,V> p =
  5. new LinkedHashMap.Entry<K,V>(hash, key, value, e);
  6. // 将节点追加到链表的尾部
  7. linkNodeLast(p);
  8. return p;
  9. }
  10. // HashMap 中的 newNode()
  11. Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
  12. return new Node<>(hash, key, value, next);
  13. }
  14. // 将节点追加到链表的尾部
  15. private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
  16. // 暂存旧的尾节点
  17. LinkedHashMap.Entry<K,V> last = tail;
  18. // 新增节点作为新的尾节点
  19. tail = p;
  20. // 旧的尾节点为 null,说明链表之前为空
  21. if (last == null)
  22. // 新增第一个节点后,首尾节点都指向第一个节点
  23. head = p;
  24. // 链表之前有数据,则直接建立新增节点和旧的尾节点的前后关系
  25. else {
  26. p.before = last;
  27. last.after = p;
  28. }
  29. }

LinkedHashMap 中重写的 newTreeNode() 底层源码实现如下:

  1. TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
  2. TreeNode<K,V> p = new TreeNode<K,V>(hash, key, value, next);
  3. // 该方法在上面 newNode() 笔记中已说明
  4. linkNodeLast(p);
  5. return p;
  6. }
  7. // HashMap 中的 newTreeNode()
  8. TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
  9. return new TreeNode<>(hash, key, value, next);
  10. }

LinkedHashMap 中重写的 afterNodeAccess() 底层源码实现如下:

  1. // 把节点移动到链表尾部
  2. void afterNodeAccess(Node<K,V> e) { // move node to last
  3. LinkedHashMap.Entry<K,V> last;
  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. }

LinkedHashMap 中重写的 afterNodeInsertion() 底层源码实现如下:

  1. // 可能会删除头节点
  2. void afterNodeInsertion(boolean evict) { // possibly remove eldest
  3. LinkedHashMap.Entry<K,V> first;
  4. // removeEldestEntry 来控制删除策略,如果链表不为空,并且删除策略允许删除的情况下,删除
  5. if (evict && (first = head) != null && removeEldestEntry(first)) {
  6. K key = first.key;
  7. // 删除头节点
  8. removeNode(hash(key), key, null, false, true);
  9. }
  10. }
  11. // 需要自己重写该方法
  12. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
  13. return false;
  14. }

LinkedHashMap按照访问顺序(查询)

  1. public V get(Object key) {
  2. Node<K,V> e;
  3. // 调用父类 HashMap 中的 getNode()
  4. if ((e = getNode(hash(key), key)) == null)
  5. return null;
  6. // 如果设置了 LRU 策略
  7. if (accessOrder)
  8. // 把查询到的节点移动到链表尾部(底层源码见上面新增部分)
  9. afterNodeAccess(e);
  10. return e.value;
  11. }

通过 afterNodeAccess() 把当前访问节点移动到链表尾部,不仅是 get() 方法,执行 getOrDefault()、compute()、computeIfAbsent()、computeIfPresent()、merge() 方法时,也会这么做,通过不断的把经常访问的节点移动到链表尾部,那么靠近链表头部的节点,自然就是很少被访问的元素了。

LinkedHashMap的访问最少删除策略

访问最少删除策略也叫做 LRU(Least recently used,最近最少使用)
访问最少删除策略大概的意思就是:经常访问的元素会被追加到队尾,这样不经常访问的数据自然就靠近队头,然后可以通过设置删除策略,比如当 Map 元素个数大于多少时,把头节点删除。
以下 demo 用于演示 LinkedHashMap 的删除策略:

  1. @Test
  2. public void test19() {
  3. LinkedHashMap<Integer, Integer> linkedHashMap
  4. = new LinkedHashMap<Integer, Integer>(4, 0.75f, true) {
  5. {
  6. put(10, 10);
  7. put(9, 9);
  8. put(20, 20);
  9. put(1, 1);
  10. }
  11. // 重写删除策略的方法,我们设定当节点个数大于 3 时,就开始删除头节点
  12. // 父类中直接返回 false,如果返回 false 代表
  13. @Override
  14. protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
  15. return size() >= 4;
  16. }
  17. };
  18. // 打印结果:{9=9, 20=20, 1=1}
  19. System.out.println(linkedHashMap);
  20. Integer integer = linkedHashMap.get(9);
  21. // 打印结果:{20=20, 1=1, 9=9}
  22. System.out.println(linkedHashMap);
  23. }

可以看到,map 初始化的时候,放进去四个元素,但结果只有三个元素,10 不见了,当 put(1, 1) 执行的时候,正好把队头的 10 删除,这个体现了达到我们设定的删除策略时,会自动的删除头节点。
当我们调用 map.get(9) 方法时,元素 9 移动到队尾,这个体现了经常被访问的节点会被移动到队尾。


LinkedHashMap的迭代器

HashMap 的笔记引用如下:
HashMap


LinkedHashMap 的迭代器源码实现如下:

  1. abstract class LinkedHashIterator {
  2. // 下一个要返回的节点
  3. LinkedHashMap.Entry<K,V> next;
  4. // 当前节点,用于 remove()
  5. LinkedHashMap.Entry<K,V> current;
  6. // 期待的版本号
  7. int expectedModCount;
  8. // 头节点作为第一个访问的节点
  9. LinkedHashIterator() {
  10. next = head;
  11. expectedModCount = modCount;
  12. current = null;
  13. }
  14. public final boolean hasNext() {
  15. return next != null;
  16. }
  17. final LinkedHashMap.Entry<K,V> nextNode() {
  18. LinkedHashMap.Entry<K,V> e = next;
  19. if (modCount != expectedModCount)
  20. throw new ConcurrentModificationException();
  21. if (e == null)
  22. throw new NoSuchElementException();
  23. current = e;
  24. // 通过链表的 after 结构,找到下一个迭代的节点
  25. next = e.after;
  26. return e;
  27. }
  28. public final void remove() {
  29. Node<K,V> p = current;
  30. if (p == null)
  31. throw new IllegalStateException();
  32. if (modCount != expectedModCount)
  33. throw new ConcurrentModificationException();
  34. current = null;
  35. K key = p.key;
  36. removeNode(hash(key), key, null, false, false);
  37. expectedModCount = modCount;
  38. }
  39. }
  40. final class LinkedKeyIterator extends LinkedHashIterator
  41. implements Iterator<K> {
  42. public final K next() { return nextNode().getKey(); }
  43. }
  44. final class LinkedValueIterator extends LinkedHashIterator
  45. implements Iterator<V> {
  46. public final V next() { return nextNode().value; }
  47. }
  48. final class LinkedEntryIterator extends LinkedHashIterator
  49. implements Iterator<Map.Entry<K,V>> {
  50. public final Map.Entry<K,V> next() { return nextNode(); }
  51. }

LinkedHashMap的常见问题

说一下你对 LinkedHashMap 的了解

LinkedHashMap 继承了 HashMap,因此它拥有 HashMap 的所有特性。
LinkedHashMap 相较于 HashMap 多了成员属性 head、tail、accessOrder,LinkedHashMap 的节点相较于 HashMap 的节点多了 before、after,因此 LinkedHashMap拥有一些特性:

  • 按照插入 / 访问顺序进行访问
  • 实现了访问最少最先删除功能,其目的是:把很久都没有访问的 key 自动删除