zoukankan      html  css  js  c++  java
  • HashMap底层代码浅解析

    我们主要看put方法,因为比较有深度。

     可以看到其中调用了putValue方法 5个参数

      参数1 Hash(key)通过计算得到Hash散列

      参数2 key:存储的key

      参数3 value:存储的Value

      参数4:代表遇到相同的key值是否替换value的值

      参数5:不太清楚。先不管

      

     1   final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
     2                    boolean evict) {
     3         Node<K,V>[] tab; Node<K,V> p; int n, i;       //这里声明了一个节点数组和一个节点,以及两个数值
     4         if ((tab = table) == null || (n = tab.length) == 0)  // 从这里看的出来。hashMap为懒加载,当对新的map中进行存储数据时,才会对map进行初始化,当表为空或者长度为0时,市容resizie进行初始化
     5             n = (tab = resize()).length;              //将初始话后hash表的容量size的值赋值给临时变量n
     6         if ((p = tab[i = (n - 1) & hash]) == null)        //判断要插入的桶是否存在节点,如果不存在,则直接把节点存储到该桶的head中  
     7             tab[i] = newNode(hash, key, value, null);      
     8         else {                            //如果既不是进行初始化,并且该桶中存在节点,那么就是发生的hash冲突。
     9             Node<K,V> e; K k;
    10             if (p.hash == hash &&
    11                 ((k = p.key) == key || (key != null && key.equals(k))))      //如果添加的值与头节点相同,则将新值放入其中,只是值未变化
    12                 e = p;
    13             else if (p instanceof TreeNode)                      //如果不同,并且该桶为红黑树状态,则条用putTreeVal添加。
    14                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    15             else {                                    //如果不同,并且桶还为链表状态
    16                 for (int binCount = 0; ; ++binCount) {            //对链表进行遍历
    17                     if ((e = p.next) == null) {                    //当节点为空时,说明没有相同的节点,将该节点存储到链表尾部
    18                         p.next = newNode(hash, key, value, null);  
    19                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st      //当该节点到大8时,就将该桶转换为红黑树
    20                             treeifyBin(tab, hash);
    21                         break;
    22                     }
    23                     if (e.hash == hash &&
    24                         ((k = e.key) == key || (key != null && key.equals(k))))   //如果此时已有相同的节点,那么跳出循环。
    25                         break;
    26                     p = e;
    27                 }
    28             }
    29             if (e != null) { // existing mapping for key          //如果有重复的节点,那么需要返回旧值
    30                 V oldValue = e.value;
    31                 if (!onlyIfAbsent || oldValue == null)      //如果 onlyifAbsent为false或者旧值为null,那么将新值插入,最后返回旧值
    32                     e.value = value;
    33                 afterNodeAccess(e);
    34                 return oldValue;
    35             }
    36         }
    37         ++modCount;        
    38         if (++size > threshold)      //当插入新节点后,判断容量如果超过阈值,则进行扩容
    39             resize();      
    40         afterNodeInsertion(evict);        //自类实现
    41         return null;
    42     }
    resize()方法   --见名知意 扩容就在这里
     1  final Node<K,V>[] resize() {
     2         Node<K,V>[] oldTab = table;      //先获取老的tab
     3         int oldCap = (oldTab == null) ? 0 : oldTab.length; //判断旧表为空则旧表容量为0.
     4         int oldThr = threshold;         //将阈值赋值给临时变量          
     5         int newCap, newThr = 0;      
     6         if (oldCap > 0) {              //判断 旧表存在
     7             if (oldCap >= MAXIMUM_CAPACITY) {   //就变存在并且容量已经到达最大,那么直接返回旧表
     8                 threshold = Integer.MAX_VALUE;    然后扩容阈值为integer的最大值、
     9                 return oldTab;              //直接返回tab
    10             }
    11             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&       //新表容量是旧表容量左移一位,相当于乘2,并且小于表的最大容量,并且旧表容量大于16,那么新表的阈值,就等于旧表的二倍
    12                      oldCap >= DEFAULT_INITIAL_CAPACITY)
    13                 newThr = oldThr << 1; // double threshold
    14         }
    15         else if (oldThr > 0) // initial capacity was placed in threshold // 旧表阈值大于0,那么将旧表的阈值赋值给新表的存储容量,说明构造函数中指定了阈值
    16             newCap = oldThr;
    17         else {                   // zero initial threshold signifies using defaults      //说明旧表不存在,则对表空间进行初始化
    18             newCap = DEFAULT_INITIAL_CAPACITY;            // 初始化容量              
    19             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //初始化阈值。阈值为容量的75%
    20         }
    21         if (newThr == 0) {          //新的阈值为0,则使用新表空间的容量乘增长因子。%75,得到新的阈值
    22             float ft = (float)newCap * loadFactor;          
    23             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
    24                       (int)ft : Integer.MAX_VALUE);
    25         }  
    26         threshold = newThr;        //将临时变量赋值给阈值
    27         @SuppressWarnings({"rawtypes","unchecked"})      //
    28             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];    //创建新表
    29         table = newTab;
    30         if (oldTab != null) {              
    31             for (int j = 0; j < oldCap; ++j) {    //对旧表进行遍历
    32                 Node<K,V> e;            //声明一个Node
    33                 if ((e = oldTab[j]) != null) { //旧表中元素若不为bull,则赋值为null,方便gc
    34                     oldTab[j] = null;     
    35                     if (e.next == null)      //如果节点只有一个元素,将旧表节点赋值给新表节点
    36                         newTab[e.hash & (newCap - 1)] = e;   
    37                     else if (e instanceof TreeNode)      //如果e节点为红黑树
    38                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
    39                     else { // preserve order          //如果该节点仍为链表
    40 Node<K,V> loHead = null, loTail = null;   41 Node<K,V> hiHead = null, hiTail = null;  
                                        那么将该节点分为低位节点,和高位节点
    42 Node<K,V> next;
                                        
    43 do {               44 next = e.next;              将节点值赋值给next

    45 if ((e.hash & oldCap) == 0) {      //对该节点hash并与旧表容量做与运算,如果为0,则 46 if (loTail == null)          //判断低位节点尾部是否为null,如果为null,则将节点赋值到低位节点的head中
                                                    
    47 loHead = e; 48 else 49 loTail.next = e;          //如果低位节点尾部不为null,则将该节点赋值到低位节点尾部的下一个元素中 50 loTail = e; 51 } 52 else {                    //不为0 53 if (hiTail == null)          //判断高位节点是否为null. 54 hiHead = e;        //为null则将该节点赋值给高位节点, 55 else 56 hiTail.next = e;      //在高位节点尾部链接上该节点 57 hiTail = e; 58 } 59 } while ((e = next) != null);      //直到该链表为节点为空 60 if (loTail != null) {        //如果低位节点尾部不为bull 61 loTail.next = null;  //将低位节点的尾部的下一个元素清空为null 62 newTab[j] = loHead;        //将低位节点的头部,赋值到新表的一个桶中, 63 } 64 if (hiTail != null) {        //高位节点不为null 65 hiTail.next = null;      //将高位节点的尾部下一个值清空为null。 66 newTab[j + oldCap] = hiHead;      将高位节点的头部,赋值给新表中高位桶中。 67 } 68 } 69 } 70 } 71 } 72 return newTab; 73 }

    如果表中节点已成为红黑树,那么直接在红黑数中进行赋值操作

     1    final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
                                                  //参数1 map ,参数2位新表,参数3为,节点所在的索引,参数4位旧表容量
    2 TreeNode<K,V> b = this;                     //头结点e          3 // Relink into lo and hi lists, preserving order           //与链表reHash中相同,将红黑树分为两个部分      4 TreeNode<K,V> loHead = null, loTail = null; 5 TreeNode<K,V> hiHead = null, hiTail = null; 6 int lc = 0, hc = 0; 7 for (TreeNode<K,V> e = b, next; e != null; e = next) {          遍历红黑树 8 next = (TreeNode<K,V>)e.next;                  //将e链的下个值赋值给next 9 e.next = null;                          //将e链的下下个节点设为空 10 if ((e.hash & bit) == 0) {                  //用e及诶按的的hash对旧有容量计算,如果为0 11 if ((e.prev = loTail) == null)      //判断低位链表尾部是否为null 12 loHead = e;                //为空,则将e节点赋值给低位节点的头节点 13 else 14 loTail.next = e;          //否则赋值给低位节点尾部节点的下一个节点 15 loTail = e;         16 ++lc;                  //低位节点长度加一 17 } 18 else { 19 if ((e.prev = hiTail) == null)        //同理 20 hiHead = e; 21 else 22 hiTail.next = e; 23 hiTail = e; 24 ++hc; 25 } 26 } 27 28 if (loHead != null) { 29 if (lc <= UNTREEIFY_THRESHOLD)          //低位节点长度不大于6则将该map转换为红海曙 30 tab[index] = loHead.untreeify(map); 31 else {           32 tab[index] = loHead;            //否则将该低位节点的头结点赋值给新表在该索引处的位置 33 if (hiHead != null) // (else is already treeified)    //如果高位节点的头节点不为null,则将该低位链表转换为红黑树 34 loHead.treeify(tab);      // 35 } 36 } 37 if (hiHead != null) { 38 if (hc <= UNTREEIFY_THRESHOLD) 39 tab[index + bit] = hiHead.untreeify(map);      高位链表类似 40 else { 41 tab[index + bit] = hiHead; 42 if (loHead != null) 43 hiHead.treeify(tab); 44 } 45 } 46 }
          如果当map容量扩大,那么reHash时,可能会有红黑树,转换为链表,此处分为高低两个链接,进程节点为存储。

    将链表结构 ,转换成红黑树

     final void treeifyBin(Node<K,V>[] tab, int hash) {    //
            int n, index; Node<K,V> e;
            if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)    //如果表为null,或者表的长度不足64,直接进行hash扩容
                resize();
            else if ((e = tab[index = (n - 1) & hash]) != null) {  //判断该节点存在数据
                TreeNode<K,V> hd = null, tl = null;
                do {
              //将node转换成treeNode TreeNode
    <K,V> p = replacementTreeNode(e, null);    if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); if ((tab[index] = hd) != null) 
              //将单链表形式的节点转换为红黑树结构。    hd.treeify(tab); } }

    参考:https://blog.csdn.net/qq_19431333/article/details/55505675

     
  • 相关阅读:
    实现类似add(1)(2)(3)的函数
    Chrome安装助手踩坑
    升级webpack4错误处理
    vue项目埋点
    如何理解vue中的v-bind?
    不能不知道的webpack基本配置
    IE9及以下浏览器升级提示
    HTML5常用API
    css中clip属性
    Web开发展望
  • 原文地址:https://www.cnblogs.com/qmk-716/p/14108853.html
Copyright © 2011-2022 走看看