zoukankan      html  css  js  c++  java
  • LinkedHashMap源码分析

    前言:LinkedHashMap继承HashMap,所以它是线程不安全的,但是它有序,下面就让我们来对其内部原理进行分析。

    注:本文jdk源码版本为jdk1.8.0_172


    1.LinkedHashMap介绍

    LinkedHashMap底层数据结构为双向链表,能保证元素按照插入顺序访问,也能以访问顺序访问,可以用来实现LRU策略缓存。

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

    可以把LinkedHashMap看成LinkedList+HashMap。由于继承HashMap所以其默认容量为16,扩容因子为0.75,非同步,允许[key,value]为null。

    2.具体源码分析

    先看LinkedHashMap的重要属性

     1 // Entry继承HashMap的Node
     2 static class Entry<K,V> extends HashMap.Node<K,V> {
     3     Entry<K,V> before, after;
     4     Entry(int hash, K key, V value, Node<K,V> next) {
     5         super(hash, key, value, next);
     6     }
     7 }
     8 /**
     9  * The head (eldest) of the doubly linked list.
    10  */
    11 // 旧数据放在head节点 
    12 transient LinkedHashMap.Entry<K,V> head;
    13 
    14 /**
    15  * The tail (youngest) of the doubly linked list.
    16  */
    17 // 新数据放在tail节点
    18 transient LinkedHashMap.Entry<K,V> tail;
    19 
    20 /**
    21  * The iteration ordering method for this linked hash map: <tt>true</tt>
    22  * for access-order, <tt>false</tt> for insertion-order.
    23  *
    24  * @serial
    25  */
    26 // false-按插入顺序存储数据 true-按访问顺序存储数据
    27 final boolean accessOrder;

    分析:

    从源码上可知以下几点:

    #1.LinkedHashMap的底层数据结构继承至HashMap的Node,并且其内部存储了前驱和后继节点。

    #2.LinkedHashMap通过accessOrder来控制元素的相关顺序,false-按插入顺序存储数据,true-按访问顺序存储数据,默认为false。

    构造函数:

     1 public LinkedHashMap() {
     2     super();
     3     accessOrder = false;
     4 }
     5 
     6   public LinkedHashMap(int initialCapacity) {
     7     super(initialCapacity);
     8     accessOrder = false;
     9 }
    10 
    11    public LinkedHashMap(int initialCapacity, float loadFactor) {
    12     super(initialCapacity, loadFactor);
    13     accessOrder = false;
    14 }
    15 
    16 public LinkedHashMap(int initialCapacity,
    17                      float loadFactor,
    18                      boolean accessOrder) {
    19     super(initialCapacity, loadFactor);
    20     this.accessOrder = accessOrder;
    21 }
    22 
    23     public LinkedHashMap(Map<? extends K, ? extends V> m) {
    24     super();
    25     accessOrder = false;
    26     putMapEntries(m, false);
    27 }

    分析:

    从构造函数可以,accessOrder默认为false,当然也可自定义。

    LinkedHashMap的实现比较精妙,很多方法都是通过HashMap中留的钩子(Hook),直接实现这些Hook就可以实现对应的功能,而不需要重写诸如put方法,因此在LinkedHashMap的源码中并未发现put方法,这里分析其实现的钩子方法。

    afterNodeAccess(Node<K,V> e),主要在执行put方法并且已存在元素时进行调用,如果accessOrder为true,会把访问到的元素移动到双向链表的末尾。

     1 void afterNodeAccess(Node<K,V> e) { // move node to last
     2         LinkedHashMap.Entry<K,V> last;
     3         // 如果accessOrder为true,并且访问的节点不是尾节点
     4         if (accessOrder && (last = tail) != e) {
     5             // 取出前驱和后继节点
     6             LinkedHashMap.Entry<K,V> p =
     7                 (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
     8             p.after = null;
     9             // p的前向节点为空,则将a赋值给头节点
    10             if (b == null)
    11                 head = a;
    12             else
    13                 // 这里其实就是把p节点从链表中移除
    14                 b.after = a;
    15             // 构建双向链表
    16             if (a != null)
    17                 a.before = b;
    18             else
    19                 last = b;
    20            // 把p节点放到双向链表尾
    21             if (last == null)
    22                 head = p;
    23             else {
    24                 p.before = last;
    25                 last.after = p;
    26             }
    27             // 尾节点为p
    28             tail = p;
    29             // 修改次数自增
    30             ++modCount;
    31             // 对于双向链表,画图可以很好理解
    32         }
    33     }

    分析:

    该函数会在调用put方法出现覆盖key操作时调用,该方法主要作用就是将访问的节点移动到双向链表的末尾,但是有附加条件accessOrder必须为true,否则该操作失效。

    afterNodeInsertion(boolean evict):该方法会在插入节点后被调用。

     1 void afterNodeInsertion(boolean evict) { // possibly remove eldest
     2     LinkedHashMap.Entry<K,V> first;
     3     // evict 驱逐的意思
     4     // 如果evict为true,其头节点不为空,其确定移除最老元素
     5     // removeEldestEntry默认返回为false,也就是不删除元素
     6     if (evict && (first = head) != null && removeEldestEntry(first)) {
     7         K key = first.key;
     8         removeNode(hash(key), key, null, false, true);
     9     }
    10 }
    11 
    12 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    13     return false;
    14 }

    分析:

    该函数的主要作用就是判断是否需要移除最老的元素,但是需要我们重写removeEldestEntry方法才能实现,因为该方法默认返回false,即不删除。

    afterNodeRemoval(Node<K,V> e):该方法会在节点被remove后调用。

     1 void afterNodeRemoval(Node<K,V> e) { // unlink
     2     // 取出其前驱和后继节点
     3     LinkedHashMap.Entry<K,V> p =
     4         (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
     5     // 把节点从双向链表中删除
     6     p.before = p.after = null;
     7     if (b == null)
     8         head = a;
     9     else
    10         b.after = a;
    11     if (a == null)
    12         tail = b;
    13     else
    14         a.before = b;
    15 }

    分析:

    该函数为典型的将双向链表的节点从链表中remove掉,逻辑还是比较简单的,理解应该不难。

    get方法:

     1 public V get(Object key) {
     2     Node<K,V> e;
     3     // 通过getNode方法取出节点,如果为null则直接返回null
     4     if ((e = getNode(hash(key), key)) == null)
     5         return null;
     6     // 如果accessOrder为true,则需要把节点移动到链表末尾
     7     if (accessOrder)
     8         afterNodeAccess(e);
     9     return e.value;
    10 }

    分析:

    通过getNode方法获取元素,该方法在HashMap中(后续会对jdk1.8的HashMap做具体分析),从这里也可以看出LinkedHashMap设计的精妙之处。

    afterNodeAccess方法前面已经分析过了,将节点移动至双向链表的尾部。

    LinkedHashMap如何实现按元素插入顺序遍历元素的,具体原理如下:

     1 // newNode函数会在put操作时被调用,LinkedHashMap重写了HashMap的newNode方法
     2 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
     3     // 创建节点
     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 
    11    private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
    12     // 取出链表尾
    13     LinkedHashMap.Entry<K,V> last = tail;
    14     // 更新链表尾部元素
    15     tail = p;
    16     // 构建双向链表
    17     if (last == null)
    18         head = p;
    19     else {
    20         p.before = last;
    21         last.after = p;
    22     }
    23 }

    分析:

    由于LinkedHashMap重写了newNode方法,在创建新的节点的时候,就会将节点挂在现有节点的尾部,从而实现按插入顺序访问元素。

    而按照访问顺序遍历元素是基于LRU算法(最近最少使用)进行遍历。

    3.总结

    LinkedHashMap继承HashMap并实现了HashMap中预留的钩子函数,因此不必重写HashMap的很多方法,设计非常巧妙。

    #1.LinkedHashMap默认容量为16,扩容因子默认为0.75,非同步,允许[key,value]为null。

    #2.LinkedHashMap底层数据结构为双向链表,可以看成是LinkedList+HashMap。

    #3.如果accessOrder为false,则可以按插入元素的顺序遍历元素,如果accessOrder为true,则可以按访问顺序遍历元素。


    by Shawn Chen,2019.09.14日,晚。

  • 相关阅读:
    软件工程概论通读第二章
    软件工程概论通读第一章
    mac 下安装mongodb
    angular5 ng-content使用方法
    angular5 @viewChild @ContentChild ElementRef renderer2
    关于日期的一篇很好的文章
    angular5 组件之间监听传值变化
    angular5 ng-bootstrap和ngx-bootstrap区别
    angular5表单验证问题
    angular5 路由变化监听
  • 原文地址:https://www.cnblogs.com/developer_chan/p/11385667.html
Copyright © 2011-2022 走看看