• hashMap 源码注释分析(二)


    接着上一篇继续

      1     /**
      2      * 返回map的长度
      3      * @return the number of key-value mappings in this map
      4      */
      5     public int size() {
      6         return size;
      7     }
      8 
      9     /**
     10      *如果map的长度为0 则返回true
     11      * @return <tt>true</tt> if this map contains no key-value mappings
     12      */
     13     public boolean isEmpty() {
     14         return size == 0;
     15     }
     16 
     17     /**
     18      * map的key 与value 都可以为 null
     19      * @see #put(Object, Object)
     20      */
     21     public V get(Object key) {
     22         Node<K,V> e;
     23         return (e = getNode(hash(key), key)) == null ? null : e.value;
     24     }
     25 
     26     /**
     27      * Implements Map.get and related methods
     28      *通过key值的hash值 与key 去查找Node
     29      * @param hash hash for key
     30      * @param key the key
     31      * @return the node, or null if none
     32      */
     33     final Node<K,V> getNode(int hash, Object key) {
     34         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
     35         if ((tab = table) != null && (n = tab.length) > 0 &&
     36             (first = tab[(n - 1) & hash]) != null) {//将table 赋值给tab table不是 null ,table 长度大于0 该hash找到的hash桶Node不为null
     37             if (first.hash == hash && // always check first node//如果是一个元素直接判断
     38                 ((k = first.key) == key || (key != null && key.equals(k))))//判断找到的桶的hash值是否等于当前要找的hash值,key值是否是自己要找的key
     39                 return first;
     40             if ((e = first.next) != null) {//判断该哈希值是否有下一个元素
     41                 if (first instanceof TreeNode)//判断该hash桶是否是红黑树
     42                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);//如果是红黑树已红黑树的方式查找
     43                 do {//不是红黑树就是链表,以链表的方式遍历查找。
     44                     if (e.hash == hash &&
     45                         ((k = e.key) == key || (key != null && key.equals(k))))
     46                         return e;
     47                 } while ((e = e.next) != null);
     48             }
     49         }
     50         return null;
     51     }
     52 
     53     /**
     54      * 判断是否包含某个key ,就是一个get 操作
     55      * @param   key   The key whose presence in this map is to be tested
     56      * @return <tt>true</tt> if this map contains a mapping for the specified
     57      * key.
     58      */
     59     public boolean containsKey(Object key) {
     60         return getNode(hash(key), key) != null;
     61     }
     62 
     63     /**
     64      * 将指定值与该映射中的指定键相关联。
     65      * 如果该映射先前包含该键的映射,则旧
     66      * 值被替换。
     67      *
     68      * @param key key with which the specified value is to be associated
     69      * @param value value to be associated with the specified key
     70      * @return the previous value associated with <tt>key</tt>, or
     71      *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     72      *         (A <tt>null</tt> return can also indicate that the map
     73      *         previously associated <tt>null</tt> with <tt>key</tt>.)
     74      */
     75     public V put(K key, V value) {
     76         return putVal(hash(key), key, value, false, true);
     77     }
     78 
     79     /**
     80      * Implements Map.put and related methods
     81      *
     82      * @param hash hash for key
     83      * @param key the key
     84      * @param value the value to put
     85      * @param onlyIfAbsent 如果 true, 不能修改覆盖当前的值
     86      * @param evict 如果 false, 当前table正在创建
     87      * @return previous value, or null if none
     88      */
     89     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
     90                    boolean evict) {
     91         Node<K,V>[] tab; Node<K,V> p; int n, i;
     92         if ((tab = table) == null || (n = tab.length) == 0)//如果table 没有初始化
     93             n = (tab = resize()).length;//扩容操作
     94         if ((p = tab[i = (n - 1) & hash]) == null)//  (n - 1) & hash 映射到一个hash桶赋值给p  ,n的值为2的幂 转为二进制就是1000..减1 后就是11111111...
     95             //这样与hash进行& 操作时,就是111111111...与hash 之间 相同位置都为1才是1,这样就可以均匀分布到了hash桶中。
     96             tab[i] = newNode(hash, key, value, null);//创建第一个hash桶
     97         else {
     98             Node<K,V> e; K k;
     99             if (p.hash == hash &&
    100                 ((k = p.key) == key || (key != null && key.equals(k))))//判断p 这个hash桶的hash值是否==当前的key
    101                 e = p;
    102             else if (p instanceof TreeNode)//如果hash桶是红黑树
    103                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//添加到树
    104             else {//是链表
    105                 for (int binCount = 0; ; ++binCount) {
    106                     if ((e = p.next) == null) {//循环判断桶中是否有下一个元素
    107                         p.next = newNode(hash, key, value, null);
    108                         if (binCount >= TREEIFY_THRESHOLD - 1) //判断是否达到转换为红黑树的阈值
    109                             treeifyBin(tab, hash);//转换为红黑树
    110                         break;
    111                     }
    112                     if (e.hash == hash &&
    113                         ((k = e.key) == key || (key != null && key.equals(k))))//如果链表中有该key值,则跳出
    114                         break;
    115                     p = e;
    116                 }
    117             }
    118             if (e != null) { // existing mapping for key
    119                 V oldValue = e.value;//把e的值赋值给 oldValue
    120                 if (!onlyIfAbsent || oldValue == null)//如果onlyIfAbsent false 表示可以覆盖
    121                     e.value = value;
    122                 afterNodeAccess(e);
    123                 return oldValue;
    124             }
    125         }
    126         ++modCount;//操作数加1 ,相当于加了一个版本号
    127         if (++size > threshold) //当size大于容器的扩容阈值
    128             resize();//开始扩容
    129         afterNodeInsertion(evict);//给LinkHashMap 使用的,在HashMap中是一个空方法
    130         return null;
    131     }
    132 
    133     /**
    134      *HashMap的扩容操作,需要进行
    135      * @return the table
    136      */
    137     final Node<K,V>[] resize() {
    138         Node<K,V>[] oldTab = table;//把当前的table 容器备份给oldTab
    139         int oldCap = (oldTab == null) ? 0 : oldTab.length;//计算容器的hash桶的数量
    140         int oldThr = threshold;//把当前的容器扩容阈值记录
    141         int newCap, newThr = 0;//初始化要创建的新生的容器的hash桶的数量与扩容阈值
    142         if (oldCap > 0) { //表示当前容器不是初生的容器
    143             if (oldCap >= MAXIMUM_CAPACITY) {//如果当前的hash桶的数量大于最大容量
    144                 threshold = Integer.MAX_VALUE;//容器扩容阈值=最大的值
    145                 return oldTab;
    146             }
    147             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
    148                      oldCap >= DEFAULT_INITIAL_CAPACITY) //如果当前的容器大小扩大1倍小于最大容量 或当前的容器的大小大于或等于初始化大小
    149                 newThr = oldThr << 1; // double threshold 新的容器的扩容阈值是当前容器的2倍
    150         }
    151         //容器初次创建
    152         else if (oldThr > 0) //当前的容器扩容阈值>0
    153             newCap = oldThr;//容器的扩容阈值=当前的容器扩容阈值
    154         else {               // 使用默认的设置
    155             newCap = DEFAULT_INITIAL_CAPACITY;
    156             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    157         }
    158 
    159         //====================
    160         if (newThr == 0) {//如果以上的操作后新的扩容阈值依然==0
    161             float ft = (float)newCap * loadFactor; //计算新的扩容阈值
    162             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
    163                       (int)ft : Integer.MAX_VALUE); //新的容器大小 小于最大容量值并且 新计算的ft < 最大容量值 ....
    164         }
    165         //开始干正事儿了.....
    166         //扩容开始...~~~
    167         threshold = newThr;//赋值容器扩容阈值
    168         @SuppressWarnings({"rawtypes","unchecked"})
    169             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    170         table = newTab;//赋值新容器
    171         if (oldTab != null) {//检查旧容器是否为空
    172             for (int j = 0; j < oldCap; ++j) {//遍历旧的hash桶
    173                 Node<K,V> e;
    174                 if ((e = oldTab[j]) != null) {//判断hash桶中有数据 ,并备份
    175                     oldTab[j] = null;
    176                     if (e.next == null)//判断是否是普通数据
    177                         newTab[e.hash & (newCap - 1)] = e;//放入新的hash桶中
    178                     else if (e instanceof TreeNode)//判断是否是红黑树
    179                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//树重新放入hash桶
    180                     else { // 剩下的一定是链表,用来解决jdk7中出现的扩容拷贝环问题。
    181                         Node<K,V> loHead = null, loTail = null;// 低 定义头尾
    182                         Node<K,V> hiHead = null, hiTail = null;// 高 定义头尾
    183                         Node<K,V> next;
    184                         /**
    185                          * oldCap一定是2的整数次幂, 这里假设是2^m 要么是0 要么是2^m
    186                          * newCap是oldCap的两倍, 则会是2^(m+1)
    187                          * 那么进行与操作的时候是什么样的呢?
    188                          * 例如:oldCap=8 ,e.hash=5(这个值随意)
    189                          * 换为二进制为 :
    190                          * oldCap:1000
    191                          * e.hash:0101
    192                          * 进行&操作会怎么样呢? 得到值为 oldCap 或 0,
    193                          * 为什么会这样? 因为 oldCap只会在最高位上一位为 1 那结果只能是这两种情况
    194                          * 那为什么要用这种方式来把原链表中的数据分成两个链表呢?
    195                          * 那就分析一下HashMap的Hash 规则 (n - 1) & hash  ,其中 n 就是 newCap 的扩容阈值 也就是会是 2^(m+1)
    196                          * 举个例子:
    197                          * 当前的二进制分别为
    198                          * oldCap:       01000
    199                          * oldCap-1:     00111
    200                          * newCap:       10000
    201                          * newCap-1:     01111
    202                          * e.hash:       00101
    203                          *
    204                          * 也就是说在 e.hash 没有改变的情况下 ,新的newCap-1比 oldCap-1 多了最高一位 1 那进行hash 计算后的情况 就只有两种 ,
    205                          * 1、跟原来的hash相同 (这样就会原位不动)
    206                          * 2、在新的容器中高位上多了一个1,那就是多了oldCap一倍  (这样就在原位置上乘以2)
    207                          *
    208                          * 那再分析一下这个结论跟使用 (e.hash & oldCap) == 0 的关系,那就是==0时, e.hash的关键一位(newCap-1比 oldCap-1 多了最高一位 1)是 0,而最高一位不是0的一定是1
    209                          * 好了,分析完毕。
    210                          */
    211                         do {
    212                             next = e.next;//备份
    213                             if ((e.hash & oldCap) == 0) {//判断如果==0 不移位
    214                                 if (loTail == null)//放到低位链表
    215                                     loHead = e;
    216                                 else
    217                                     loTail.next = e;
    218                                 loTail = e;
    219                             }
    220                             else {//否则 移位到高位
    221                                 if (hiTail == null)//放到高位链表
    222                                     hiHead = e;
    223                                 else
    224                                     hiTail.next = e;
    225                                 hiTail = e;
    226                             }
    227                         } while ((e = next) != null);
    228                         if (loTail != null) {
    229                             loTail.next = null;
    230                             newTab[j] = loHead;//原位不动
    231                         }
    232                         if (hiTail != null) {
    233                             hiTail.next = null;
    234                             newTab[j + oldCap] = hiHead;//位置加oldCap一倍
    235                         }
    236                     }
    237                 }
    238             }
    239         }
    240         return newTab;
    241     }

    超详细的HashMap 源码解读

  • 相关阅读:
    spring boot 配置时区差别
    Java 中序列化与反序列化引发的思考?
    Http 状态码总结
    Springboot
    ConcurrentHashMap 实现缓存类
    C#基础(二)拆箱与装箱,循环与选择结构,枚举
    C#基础知识(一)自己总结的。。。
    贪吃蛇大作战双人版加强版完整代码加详细步骤,不懂问博主 啦啦啦。
    C#数组随机生成四个随机数
    C#添加背景音乐
  • 原文地址:https://www.cnblogs.com/hxz-nl/p/11951574.html
Copyright © 2020-2023  润新知