• LinkedHashMap源码解析


    这篇文章只志在搞懂一个问题,LinkedHashMap是怎么实现顺序遍历HashMap的呢?

    先来看下LinkedHashMap的基本结构。

    1. public class LinkedHashMap<K,V>
    2. extends HashMap<K,V>
    3. implements Map<K,V>
    4. {/****/}

    LinkedHashMap,继承了HashMap,说明结构与HashMap一致。

    从上一篇文章可以知道HashMap之所以不能顺序遍历,是因为它的插入顺序就是无序的。但是LinkedHashMap却实现了顺序遍历,它是怎么实现的呢?

    想要搞清楚LinkedHashMap是怎么实现顺序遍历的,直接先看下它是如何实现迭代器的。

    图中LinkedHashMap自己实现了几个迭代器。LinkedEntryIterator,LinkedHashIterator,LinkedKeyIterator,LinkedValueIterator。

    其中LinkedHashIterator是基类,LinkedEntryIterator(遍历Map.Entry,即每一个key,value)和LinkedKeyIterator(遍历Key),LinkedValueIterator(遍历value)都继承了LinkedHashIterator。

    先来看下基类。

    1. // LinkedHashMap.java
    2. abstract class LinkedHashIterator {
    3. LinkedHashMapEntry<K,V> next;
    4. LinkedHashMapEntry<K,V> current;
    5. int expectedModCount;
    6. LinkedHashIterator() {// 初始化next
    7. next = head;
    8. expectedModCount = modCount;
    9. current = null;
    10. }
    11. public final boolean hasNext() {// 遍历的结束条件
    12. return next != null;
    13. }
    14. // nextNode方法没什么特别的,就是简单的遍历链表而已
    15. // 唯一需要注意的是,这里nextNode返回的是LinkedHashMapEntry,
    16. // 而不是HashMap的Node
    17. final LinkedHashMapEntry<K,V> nextNode() {// 遍历链表
    18. LinkedHashMapEntry<K,V> e = next;
    19. if (modCount != expectedModCount)
    20. throw new ConcurrentModificationException();
    21. if (e == null)
    22. throw new NoSuchElementException();
    23. current = e;
    24. next = e.after;
    25. return e;
    26. }
    27. public final void remove() {
    28. Node<K,V> p = current;
    29. if (p == null)
    30. throw new IllegalStateException();
    31. if (modCount != expectedModCount)
    32. throw new ConcurrentModificationException();
    33. current = null;
    34. K key = p.key;
    35. removeNode(hash(key), key, null, false, false);
    36. expectedModCount = modCount;
    37. }
    38. }
    39. // 通用调用了nextNode的方法
    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. }

    从LinkedKeyIterator和LinkedValueIterator和LinkedEntryIterator迭代器的实现可以看出,实现遍历都是调用的基类的nextNode方法。

    而基类的nextNode方法,只是做了简单的链表遍历。而且是一个完整的链表,跟HashMap中为了解决哈希冲突而构建的链表不同。初次之外,返回的也是LinkedHashMapEntry,而不是HashMap中的Node。

    所以,可以知道秘密实际就在LinkedHashMapEntry当中。

    下面看下LinkedHashMapEntry做了什么?

    1. static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> {
    2. LinkedHashMapEntry<K,V> before, after;
    3. LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) {
    4. super(hash, key, value, next);
    5. }
    6. }

    LinkedHashMapEntry也特别简单,就是存储了一个before,after的指针。

    从这里其实可以猜测,LinkedHashMap实际上应该是在put的过程中,顺便构建了一个双链表。

    这样,在插入,查询的时候,可以使用HashMap的快速,而在遍历的时候,就根据这个链表进行遍历。简直完美!

    ->_->唯一的不足就是耗费了空间而已。但是哪有这么多两全其美的事情呢~

    1. // LinkedHashMap.java
    2. // head的赋值,newNode在HashMap的putVal方法也会调用
    3. Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    4. LinkedHashMapEntry<K,V> p =
    5. new LinkedHashMapEntry<K,V>(hash, key, value, e);
    6. linkNodeLast(p);// 在创建一个新建节点的同时,构建p和链表的最后一个节点的关系:p.before = last;last.after = p;构建双链表
    7. return p;
    8. }
    9. // 构建双链表
    10. private void linkNodeLast(LinkedHashMapEntry<K,V> p) {
    11. LinkedHashMapEntry<K,V> last = tail;
    12. tail = p;
    13. if (last == null)
    14. head = p;
    15. else {
    16. p.before = last;
    17. last.after = p;
    18. }
    19. }

    LinkedHashMap没有重写putVal方法,所以看HashMap的putVal方法可以知道newNode是用来创建一个新节点的。

    所以到这里就可以得出结论了。

    LinkedHashMap在HashMap的基础上实现了记录顺序的功能。实现的方式是,重写了newNode方法,返回了一个新的数据结构:LinkedHashMapEntry。

    LinkedHashMapEntry记录了prev,和next Entry。newNode方法会在put()方法的时候被调用,也就是,每次向HashMap添加元素的时候,就会记录添加的这个节点的prev和prev的next。

     

  • 相关阅读:
    0基础编程教学,编程零基础该学什么,中文编程工具下载
    信号与线性时不变系统的傅里叶描述
    HFish蜜罐实践:网络安全防御的主动出击
    如何把网站的http改成https?
    《机器人SLAM导航核心技术与实战》第1季:第6章_机器人底盘
    一个全局最优化的方法:随机游走算法-Random-Walk
    【FPGA教程案例22】基于FIFO核的可控任意长度延迟器设计
    JavaEE面试题
    地图:leaflet基本使用
    『力扣每日一题06』字符串中的第一个唯一字符
  • 原文地址:https://blog.csdn.net/yolan6824/article/details/125461680