• Java 容器 LinkedHashMap源码分析2


    一、类签名

    LinkedHashMap<K,V>继承自HashMap<K,V>,可知存入的节点key永远是唯一的。可以通过Android的LruCache了解LinkedHashMap用法。

    1 public class LinkedHashMap<K,V>
    2     extends HashMap<K,V>
    3     implements Map<K,V>

    HashMap_UML

    二、节点

    Entry<K,V>HashMap.Node<K,V>的子类,增加beforeafter引用实现双向链表

    1 static class Entry<K,V> extends HashMap.Node<K,V> {
    2     // 前节点、后节点
    3     Entry<K,V> before, after;
    4 
    5     Entry(int hash, K key, V value, Node<K,V> next) {
    6         // 调用HashMap构造方法
    7         super(hash, key, value, next);
    8     }
    9 }

    HashMap_UML

    三、数据成员

    双向链表头,指向最早(最老)访问节点元素

    transient LinkedHashMap.Entry<K,V> head;
    

    双向链表尾,指向最近(最晚)访问节点元素

    transient LinkedHashMap.Entry<K,V> tail;
    

    是否保持访问顺序,为true则每次被访问的节点都会放到链表尾部

    final boolean accessOrder;
    

    依次插入Entry_0Entry_5,当accessOrder为true并访问Entry_4时,Entry_4会被移到链尾

    HashMap_UML

    四、构造方法

     1 public LinkedHashMap(int initialCapacity, float loadFactor) {
     2     super(initialCapacity, loadFactor);
     3     accessOrder = false;
     4 }
     5 
     6 public LinkedHashMap(int initialCapacity) {
     7     super(initialCapacity);
     8     accessOrder = false;
     9 }
    10 
    11 public LinkedHashMap() {
    12     super();
    13     accessOrder = false;
    14 }
    15 
    16 public LinkedHashMap(Map<? extends K, ? extends V> m) {
    17     super();
    18     accessOrder = false;
    19     putMapEntries(m, false);
    20 }
    21 
    22 // 维持存取顺序仅能通过此构造方法
    23 public LinkedHashMap(int initialCapacity,
    24                      float loadFactor,
    25                      boolean accessOrder) {
    26     super(initialCapacity, loadFactor);
    27     this.accessOrder = accessOrder;
    28 }

    五、成员方法

     1 // 把节点插入到链表尾部
     2 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
     3     LinkedHashMap.Entry<K,V> last = tail;
     4     tail = p;
     5     // 如果尾节点为空表明链表没有元素,则p就是头结点
     6     if (last == null)
     7         head = p;
     8     else {
     9         // 处理双向链表节点
    10         p.before = last;
    11         last.after = p;
    12     }
    13 }
    14 
    15 // apply src's links to dst
    16 private void transferLinks(LinkedHashMap.Entry<K,V> src,
    17                            LinkedHashMap.Entry<K,V> dst) {
    18     LinkedHashMap.Entry<K,V> b = dst.before = src.before;
    19     LinkedHashMap.Entry<K,V> a = dst.after = src.after;
    20     if (b == null)
    21         head = dst;
    22     else
    23         b.after = dst;
    24     if (a == null)
    25         tail = dst;
    26     else
    27         a.before = dst;
    28 }
    29 
    30 // 重写HashMap钩子方法
    31 void reinitialize() {
    32     super.reinitialize();
    33     head = tail = null;
    34 }
    35 
    36 // 创建新链表节点
    37 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    38     LinkedHashMap.Entry<K,V> p =
    39         new LinkedHashMap.Entry<>(hash, key, value, e);
    40     linkNodeLast(p);
    41     return p;
    42 }
    43 
    44 // 替换链表节点
    45 Node<K,V> replacementNode(Node<K,V> p, Node<K,V> next) {
    46     LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
    47     LinkedHashMap.Entry<K,V> t =
    48         new LinkedHashMap.Entry<>(q.hash, q.key, q.value, next);
    49     transferLinks(q, t);
    50     return t;
    51 }
    52 
    53 // 创建新红黑树节点
    54 TreeNode<K,V> newTreeNode(int hash, K key, V value, Node<K,V> next) {
    55     TreeNode<K,V> p = new TreeNode<>(hash, key, value, next);
    56     linkNodeLast(p);
    57     return p;
    58 }
    59 
    60 // 替换红黑树节点
    61 TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
    62     LinkedHashMap.Entry<K,V> q = (LinkedHashMap.Entry<K,V>)p;
    63     TreeNode<K,V> t = new TreeNode<>(q.hash, q.key, q.value, next);
    64     transferLinks(q, t);
    65     return t;
    66 }

    六、顺序操作

     1 // 把节点从链表解除链接
     2 void afterNodeRemoval(Node<K,V> e) {
     3     // p:即是节点e
     4     // b:e的前一个节点
     5     // a:e的后一个节点
     6     LinkedHashMap.Entry<K,V> p =
     7         (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
     8 
     9     // 置空节点p的前后引用
    10     p.before = p.after = null;
    11     
    12     if (b == null) {
    13         // 可知节点e是链表头结点,则e的下一个节点a作为链表的头结点
    14         head = a;
    15     } else {
    16         // 可知节点e本是中间结点,把e下一个节点a作为e上一个节点的后续节点
    17         b.after = a;
    18     }
    19 
    20     if (a == null) {
    21         // 可知节点e本是链表尾节点,则e的上一个节点b作为链表的尾节点
    22         tail = b;
    23     } else {
    24         // 可知节点e本身是中间节点,把e上一个节点b作为e下一个节点的前置节点
    25         a.before = b;
    26     }
    27 }
    28 
    29 // 父类HashMap调用putVal()中会调用此方法,移除最少使用的节点
    30 void afterNodeInsertion(boolean evict) {
    31     // first是最少使用的节点
    32     LinkedHashMap.Entry<K,V> first;
    33     if (evict && (first = head) != null && removeEldestEntry(first)) {
    34         // 获取first的key
    35         K key = first.key;
    36         // 通过key找到对应Node并移除
    37         removeNode(hash(key), key, null, false, true);
    38     }
    39 }
    40 
    41 // 把节点移动到链表尾
    42 void afterNodeAccess(Node<K,V> e) {
    43     LinkedHashMap.Entry<K,V> last;
    44     // 仅当accessOrder为true且被访问元素不是尾节点
    45     if (accessOrder && (last = tail) != e) {
    46         // p:即是节点e
    47         // b:e的前一个节点
    48         // a:e的后一个节点
    49         LinkedHashMap.Entry<K,V> p =
    50             (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
    51 
    52         // 置空节点p的后引用
    53         p.after = null;
    54 
    55         if (b == null) {
    56             // 可知节点e本是头结点,则e的下一个节点a作为链表的头结点
    57             head = a;
    58         } else {
    59             // 可知节点e本是中间结点,把e下一个节点a作为e上一个节点的后续节点
    60             b.after = a;
    61         }
    62 
    63         if (a != null) {
    64             // 可知节点e本身是中间节点,把e上一个节点b作为e下一个节点的前置节点
    65             a.before = b;
    66         } else {
    67             // 可知节点e本是尾节点,则e的上一个节点b作为链表的尾节点
    68             last = b;
    69         }
    70         
    71         // p之前没有节点,表明p就是头结点
    72         if (last == null) {
    73             head = p;
    74         } else {
    75             // p作为新的尾节点,链接到上一个尾节点之后
    76             p.before = last;
    77             last.after = p;
    78         }
    79 
    80         tail = p; // tail引用指向p
    81         ++modCount; // 修改次数递增
    82     }
    83 }

    七、获取

     1 // 检查LinkedHashMap是否包含指定value
     2 public boolean containsValue(Object value) {
     3     // 从链表头结点开始遍历,逐个查找Entry.value是否等于value
     4     for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after) {
     5         V v = e.value;
     6         if (v == value || (value != null && value.equals(v)))
     7             return true; // 包含对应value,返回true
     8     }
     9     return false; // 不包含对应value,返回false
    10 }
    11 
    12 // 通过Key获取对应Entry的value
    13 public V get(Object key) {
    14     Node<K,V> e;
    15     if ((e = getNode(hash(key), key)) == null) {
    16         // 通过key获取Node为空,返回null作为结果
    17         return null;
    18     }
    19 
    20     if (accessOrder) {
    21         // 通过key获取Node不为空,执行afterNodeAccess(e)调整顺序
    22         afterNodeAccess(e);
    23     }
    24 
    25     return e.value; // 最后把获取的Entry.value返回
    26 }
    27 
    28 // 通过Key获取对应Entry的value
    29 public V getOrDefault(Object key, V defaultValue) {
    30    Node<K,V> e;
    31     if ((e = getNode(hash(key), key)) == null) {
    32        // 通过key获取Node为空,返回defaultValue作为结果
    33        return defaultValue;
    34     }
    35     
    36     if (accessOrder) {
    37         // 通过key获取Node不为空,执行afterNodeAccess(e)调整顺序
    38         afterNodeAccess(e);
    39     }
    40 
    41    return e.value; // 最后把获取的Entry.value返回
    42 }

    八、移除

     1 // 清除所有引用
     2 public void clear() {
     3     super.clear(); // 把HashMap所有Entry都清空
     4     head = tail = null;  // 置空head和tail引用
     5 }
     6 
     7 // 方法主要用于被子类重写,决定最少使用的节点能否被移除
     8 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
     9     return false;
    10 } 

  • 相关阅读:
    IIS 禁止自动回收
    误删除系统帐户且SA被禁用,如何修复?单用户模式登录可解决。
    不到100行代码实现SHELL环境下图形化的IP设置功能
    2021总结
    快排
    Windows 10 修改用户名及Users文件夹
    DATUM支持不同格式(JSON,CSV,SQL,XML等)的在线模拟数据生成器
    记:云服务器被暴破感染bioset
    前端项目修改npm/yarn配置文件来加速包下载
    十个前端常见手写功能
  • 原文地址:https://www.cnblogs.com/linghu-java/p/10728384.html
Copyright © 2020-2023  润新知