zoukankan      html  css  js  c++  java
  • HashMap

    HashMap

            实现了 Map<K,V> 接口, HashTable实现了Dictionary<K,V>

            数据结构 : 数组 + 链表 + 红黑树(增加查询速度)

            基本使用方法 :

         1 . 从测试用例开始查看HashMap的源码

             当然在查看前 , 我们先来认识几个我们需要知道的成员变量.

             static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   // 默认初始化可存放元素个数

             static final int MAXIMUM_CAPACITY = 1 << 30;    // 数组内最大存放元素个数

             static final float DEFAULT_LOAD_FACTOR = 0.75f;   //负载因子

             static final int UNTREEIFY_THRESHOLD = 6;    //当链表少于6个的时候, 会再次转为链表

             static final int MIN_TREEIFY_CAPACITY = 64;  // 当一个链表个数达到8,会尝试将其变为红黑树,但是如果这个时候map内元素的总个数 < 64 , 会优先考虑扩容.

       transient Node<K,V>[] table;      // 操作的数组

     

     

     

     a . 直接开始看第三个构造方法.

          1. 首先校验初始化元素个数是否 < 0 ,  true 的话 , 就抛出异常.

          2. 初始化的元素个数是否已经超出了最大值, 如果超出,按最大值来算.

          3. 负载因子是否为 < = 0 或者 不是个Float类型的数字. ( NaN = Not a Number ) , 为true ,抛出异常

          4. 在为 threshold 这个元素赋值的时候, 需要对这个元素重新计算. 要求必须为2的倍数

              当为2的倍数的时候,  key的hash值 % 数组长度 = key的hash值 & (n-1)  . n是数组长度 .

              包括后面扩容时,高位链与低位链的使用.

         

      2. 接下来开始看 put 方法.

            

          

            a . 直接调用了 putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) 方法. 我们先来看一下这个方法的4个参数

                1. hash  . 是将key进行hash运算后的结果. 这里我们可以看到 HashMap的 key 和 value都是允许为空的,如果key为null ,默认存储在数组的第一位.

               

                 2. 这里补充一个点,当数组的Key为一个对象的时候,因为在我们实际的场景中,希望只要对象内属性一样,就默认为同一个.所以我们就需要重写这个对象的 hashCode()方法了.

                 3. 这里的key进行hash()计算,不仅仅是进行了一个hashCode(), 因为是int类型, 这个还将计算出来的结果像右移16位,让高位和地位进行了异或,使高位和地位都参与了运算,在位桶内分布更加均匀.

         b. 第二个参数就是value,我们传输的值

         c . Boolean onlyIfAbsent = false .当这个元素为false的时候,默认key相同时,会对value进行覆盖.为true,则保持第一次的value值不变

         d . evict if false, the table is in creation mode. 默认为true

     

       2. 开始看 putVal方法.

                源码如下 :

      

     

     

    a .  首先判断成员变量 Table 是否为Null ,或者当前数组长度为0 , 那么就需要进行扩容(其实这个时候叫初始化比较合适.)

          进入resize()方法

          初始化进入的时候(见下图代码),

         1.  首先将 成员变量数组 table赋值给 oldTab (当前未扩容的数组)

         2.  oldCap = oldTab.length   (初始化时为 0) 未扩容前数组的长度. 

         3.  threshold , 当我们传输数组初始化长度的时候,这里会将其变为2的幂次方. 比如我们传输的是10 , 那么这里就是16. 如果我们不传输 ,这里就是0 (int类型的默认值) .

         4 . 目前table还是null , 第一个if(old>cap) ,此方法暂时不会进入.

         5. else if ( oldThre > 0) , 如果我们传输的默认大小 10 (变为16了) , 那么现在 newCap = 16

         6 . 此时newThr 还是默认值 0 , 进入 if ( newThr == 0 ) , float ft = 16 * 0.75  , 然后重新赋值给 threshold .

         7. 同时table= new Node[16] ;

         8. 这个时候 table就初始化好了. 后面暂时不用看,因为我们的 oldTable == null

        

     

       a. 接下来原始流程继续往下看.

     

      a . 如上图, 1 的值为当前key所在位桶的索引

      b. 2的值为table在这个索引上存储的头结点Node .

      c. 如果头结点都为Null ,说明当前位桶无元素,直接当前位桶新增一个Node. Node的值为null .  tab[i] = newNode(hash, key, value, null);  

          顺便这里描述一下Node. 这是存在于HashMap的一个静态内部类, 有hash , key , value , next 属性.

     d. 继续往下看,如果头结点不为空.

     

     进入到 else 中.

     

     a . 如果hash值一样(与hashcode()方法有关系), 并且 (k == p.key 或者 key.equals(K) , 这里又用到了equals方法) , 所以当HashMap的key是对象的时候, 我们需要重写这个对象的 hashcode() 方法 和 equasl()方法.

     b . 如果此位桶的头结点 instanceof TreeNode (红黑树类型) , 那就采用 putTreeVal 方法加入到红黑树内.

     c . 接下来进入最后一个else.

     

     a . 说明此位桶上, 链表不为空  且长度不超过 8 , 那就需要开始for循环这个链表覆盖或者新增元素Node了.

     b .  先看for循环内的第一个if . 如果当前阶段的next为null,直接加入new Node() 加入链表的尾端 (多线程情况下,无锁控制,容易出现覆盖Node被覆盖.尾插法) , 如果插入之前的长度 >=7 , 那么这时候就需要尝试将链表改为红黑数了.结束后,跳出for循环.

     c .  第二个if, 如果链表中存在一个元素Key相同,将这个元素赋值给key,跳出for循环.

     d . 继续

     a . 如果e != Null , 说明存在重复的元素.

      这个时候就说到我们之前说的 onlyIfAbsent , 现在默认是 false , 会进行覆盖. 并将oldValue返回. 因为是覆盖,所以不需要 size++ .

     b . 如果为空,说明就是新增了一个元素, 成员变量 size++ .(所以说是非线程安全的)

     c .如果增加之后的size > 临界值 threshold (这个时候就是 16*0.75 = 12) , 这个时候就是真正的扩容了.

     d. 扩容结束,返回null.

     e . 接下来看一下 从 16 扩充到32 的时候,是如何扩容的 (新的数组 位桶为32 , 最大存储容量 = 32 *0.75 = 24) . 再次查看resize方法.

       

    人总得做点什么 ,不是么
  • 相关阅读:
    最近玩Bootstrap , 一些小工具 记录在案。
    测试word发表博客
    Linux at 定时任务
    Linux查看磁盘目录内存空间使用情况
    R生存分析AFT
    Accelerated Failure Time Models加速失效时间模型AFT
    Shell sleep指定延迟时间
    Shell脚本导入外部脚本内容
    Shell输入输出重定向
    Shell while
  • 原文地址:https://www.cnblogs.com/liweibing/p/12856317.html
Copyright © 2011-2022 走看看