范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

hashMap源码解析

  问题
  此次主要分析1.8的版本, 并对1.8之前的结构做对比 hashMap结构 hashMap put的过程 hashMap 扩容的方式 hashMap的并发问题 1.8版本以及1.7版本的差异 hashMap的实现原理
  链表转成红黑树
  HashMap是一张哈希表(即数组),表中的每个元素都是键值对(Map.Entry类)。 当hash碰撞时。使用单向链表存储,新元素放在队尾 当单个链表大于8个时,转为红黑树进行存储,当链表长度小于6时,红黑树会重新变回链表 当链表数组的容量超过初始容量的0.75时,再散列将链表数组扩大2倍,把每一个链表分成奇偶两个子链表分别挂在新链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率 HashMap数据结构
  node结构 // Node是单向链表,它实现了Map.Entry接口     static class Node implements Map.Entry {         final int hash;         final K key;         V value;         Node next;          Node(int hash, K key, V value, Node next) {             this.hash = hash;             this.key = key;             this.value = value;             this.next = next;         }         // 略     }
  红黑树结构 static final class TreeNode extends LinkedHashMap.Entry {         TreeNode parent;  // red-black tree links         TreeNode left;         TreeNode right;         TreeNode prev;    // needed to unlink next upon deletion         boolean red;         TreeNode(int hash, K key, V val, Node next) {             super(hash, key, val, next);         }         // 略 什么是红黑树
  是一种平衡二叉树 节点是红色或者黑色 根节点是黑色 每个叶子的节点都是黑色的空节点 每个红色节点的两个子节点都是黑色的 从任意节点到其每个叶子的所有路径都包含相同的黑色节点
  通过左旋转或右旋转满足以上规则,保证深度相同 读写过程写入过程public V put(K key, V value) {           return putVal(hash(key), key, value, false, true);       }        /**       * Implements Map.put and related methods       *       * @param hash hash for key       * @param key the key       * @param value the value to put       * @param onlyIfAbsent if true, don"t change existing value       * @param evict if false, the table is in creation mode.       * @return previous value, or null if none       */   final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                      boolean evict) {           Node[] tab;        Node p;        int n, i;           if ((tab = table) == null || (n = tab.length) == 0)               n = (tab = resize()).length;       /*如果table的在(n-1)&hash的值是空,就新建一个节点插入在该位置*/           if ((p = tab[i = (n - 1) & hash]) == null)               tab[i] = newNode(hash, key, value, null);       /*表示有冲突,开始处理冲突*/           else {               Node e;            K k;       /*检查第一个Node,p是不是要找的值*/               if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))                   e = p;               else if (p instanceof TreeNode)                   e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);               else {                   for (int binCount = 0; ; ++binCount) {           /*指针为空就挂在后面*/                       if ((e = p.next) == null) {                           p.next = newNode(hash, key, value, null);                  //如果冲突的节点数已经达到8个,看是否需要改变冲突节点的存储结构,                            //treeifyBin首先判断当前hashMap的长度,如果不足64,只进行                           //resize,扩容table,如果达到64,那么将冲突的存储结构为红黑树                           if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                               treeifyBin(tab, hash);                           break;                       }           /*如果有相同的key值就结束遍历*/                       if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))                           break;                       p = e;                   }               }       /*就是链表上有相同的key值*/               if (e != null) { // existing mapping for key,就是key的Value存在                   V oldValue = e.value;                   if (!onlyIfAbsent || oldValue == null)                       e.value = value;                   afterNodeAccess(e);                   return oldValue;//返回存在的Value值               }           }           ++modCount;        /*如果当前大小大于门限,门限原本是初始容量*0.75*/           if (++size > threshold)               resize();//扩容两倍           afterNodeInsertion(evict);           return null;       } HashMap的键值存储
  我们给 put() 方法传递键和值时,我们先对键调用 hashCode() 方法,计算并返回 hashCode,然后使用HashMap内部的hash算法,将hashCode计算为表中的具体位置,找到 Map 数组的 bucket 位置来储存 Node 对象。 解决Hash碰撞
  如果hash到的数组位置已存在对象,即为Hash碰撞。JDK使用拉链法解决Hash碰撞问题。
  即以原有的Node节点为基础,构造链表。将新的Node节点挂在后面。 链表过长导致的复杂度问题
  HashMap的查询操作最佳时间复杂度是O(1),但是当表中的某个链表过长时,查询该链表上的元素时间复杂度为O(n)。JDK1.8中解决了该问题,当HashMap中某链表长度大于8时,链表会重构为红黑树,这样,HashMap的最坏时间复杂度为O(n)。同理,为了不必要的消耗,当链表长度小于6时,红黑树会重新变回链表 读取过程public V get(Object key) {           Node e;           return (e = getNode(hash(key), key)) == null ? null : e.value;       }         /**       * Implements Map.get and related methods       *       * @param hash hash for key       * @param key the key       * @return the node, or null if none       */       final Node getNode(int hash, Object key) {           Node[] tab;//Entry对象数组       Node first,e; //在tab数组中经过散列的第一个位置       int n;       K k;       /*找到插入的第一个Node,方法是hash值和n-1相与,tab[(n - 1) & hash]*/       //也就是说在一条链上的hash值相同的           if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {       /*检查第一个Node是不是要找的Node*/               if (first.hash == hash && // always check first node                   ((k = first.key) == key || (key != null && key.equals(k))))//判断条件是hash值要相同,key值要相同                   return first;         /*检查first后面的node*/               if ((e = first.next) != null) {                   if (first instanceof TreeNode)                       return ((TreeNode)first).getTreeNode(hash, key);                   /*遍历后面的链表,找到key值和hash值都相同的Node*/                   do {                       if (e.hash == hash &&                           ((k = e.key) == key || (key != null && key.equals(k))))                           return e;                   } while ((e = e.next) != null);               }           }           return null;       }
  get(key)方法时获取key的hash值,计算hash&(n-1)得到在链表数组中的位置first=tab[hash&(n-1)],先判断first的key是否与参数key相等,不等就遍历后面的链表找到相同的key值返回对应的Value值即可 HashMap的扩容 final Node[] resize() {          Node[] oldTab = table;          int oldCap = (oldTab == null) ? 0 : oldTab.length;          int oldThr = threshold;          int newCap, newThr = 0;          /*如果旧表的长度不是空*/          if (oldCap > 0) {              if (oldCap >= MAXIMUM_CAPACITY) {                  threshold = Integer.MAX_VALUE;                  return oldTab;              }   /*把新表的长度设置为旧表长度的两倍,newCap=2*oldCap*/              else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&                       oldCap >= DEFAULT_INITIAL_CAPACITY)         /*把新表的门限设置为旧表门限的两倍,newThr=oldThr*2*/                  newThr = oldThr << 1; // double threshold          }       /*如果旧表的长度的是0,就是说第一次初始化表*/          else if (oldThr > 0) // initial capacity was placed in threshold              newCap = oldThr;          else {               // zero initial threshold signifies using defaults              newCap = DEFAULT_INITIAL_CAPACITY;              newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);          }                               if (newThr == 0) {              float ft = (float)newCap * loadFactor;//新表长度乘以加载因子              newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?                        (int)ft : Integer.MAX_VALUE);          }          threshold = newThr;          @SuppressWarnings({"rawtypes","unchecked"})   /*下面开始构造新表,初始化表中的数据*/          Node[] newTab = (Node[])new Node[newCap];          table = newTab;//把新表赋值给table          if (oldTab != null) {//原表不是空要把原表中数据移动到新表中                  /*遍历原来的旧表*/                    for (int j = 0; j < oldCap; ++j) {                  Node e;                  if ((e = oldTab[j]) != null) {                      oldTab[j] = null;                      if (e.next == null)//说明这个node没有链表直接放在新表的e.hash & (newCap - 1)位置                          newTab[e.hash & (newCap - 1)] = e;                      else if (e instanceof TreeNode)                          ((TreeNode)e).split(this, newTab, j, oldCap);   /*如果e后边有链表,到这里表示e后面带着个单链表,需要遍历单链表,将每个结点重*/                      else { // preserve order保证顺序                   ////新计算在新表的位置,并进行搬运                          Node loHead = null, loTail = null;                          Node hiHead = null, hiTail = null;                          Node next;                                                 do {                              next = e.next;//记录下一个结点             //新表是旧表的两倍容量,实例上就把单链表拆分为两队,                //e.hash&oldCap为偶数一队,e.hash&oldCap为奇数一对                              if ((e.hash & oldCap) == 0) {                                  if (loTail == null)                                      loHead = e;                                  else                                      loTail.next = e;                                  loTail = e;                              }                              else {                                  if (hiTail == null)                                      hiHead = e;                                  else                                      hiTail.next = e;                                  hiTail = e;                              }                          } while ((e = next) != null);                                                 if (loTail != null) {//lo队不为null,放在新表原位置                              loTail.next = null;                              newTab[j] = loHead;                          }                          if (hiTail != null) {//hi队不为null,放在新表j+oldCap位置                              hiTail.next = null;                              newTab[j + oldCap] = hiHead;                          }                      }                  }              }          }          return newTab;      } 扩容时机
  当size超过阈值(数组长度负载因子*)时,即开始扩容,HashMap的负载因子为0.75。 为何要数组未满就扩容
  避免频繁出现Hash碰撞,造成拉链过长(红黑树过长)。这样会导致查询复杂度频繁出现最坏情况 扩容过程
  创建原本数组容量*2的新数组,将节点从原本的数组中迁移过去。 jdk1.7的问题put()时,HashMap会先遍历table数组,用hash值和equals()判断数组中是否存在完全相同的key对象, 如果这个key对象在table数组中已经存在,就用新的value代替老的value。如果不存在,就创建一个新的Entry对象添加到table[ i ]处。如果该table[ i ]已经存在其他元素,那么新Entry对象将会储存在bucket链表的表头,通过next指向原有的Entry对象,形成链表结构(hash碰撞解决方案) 扩容时,将原先table的元素全部移到newTable里面,重新计算hash,然后再重新根据hash分配位置。
  问题点:两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了 为什么线程不安全
  HashMap 在并发时可能出现的问题主要是两方面:
  如果多个线程同时使用 put 方法添加元素,而且假设正好存在两个 put 的 key 发生了碰撞(根据 hash 值计算的 bucket 一样),那么根据 HashMap 的实现,这两个 key 会添加到数组的同一个位置,这样最终就会发生其中一个线程 put 的数据被覆盖
  如果多个线程同时检测到元素个数超过数组大小 * loadFactor,这样就会发生多个线程同时对 Node 数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给 table,也就是说其他线程的都会丢失,并且各自线程 put 的数据也丢失

演员涓子事业巅峰时嫁圈外丈夫,先后生两子,46岁依然优雅美丽2022年,脱贫攻坚剧大山的女儿一经播出,便引起观众的积极反馈。主演杨蓉将黄文秀诠释得淋漓尽致,不少人看完这部剧忍不住红了眼眶。不仅主演演技过关,大山的女儿这部剧里的配角个个来头不我愿称它为4399游戏盒模拟器,经营一家游戏平台也太难了吧在游戏尚未出名或是根部不会火起来的世界中,有这么一些团体被称为小众圈子。小众,并不一定是代表这个游戏质量不行,很有可能是其作者独特的脑回路只能引起一小部分人共鸣。这就诞生了一种小众我的世界基岩版基础指令小皮果吖编。一切指令符合由英文符号组成。其中!表明除以外。指令方块获得give玩家名commandblock传送tp玩家名坐标经验xp数字玩家名填充fill坐标1坐标2复制clon闪耀暖暖五花肉月饼食谱配方一览闪耀暖暖游戏中五花肉月饼由豆沙五仁五花肉制作而成,那么今天小编就给大家介绍一下闪耀暖暖五花肉月饼食谱配方一览,有需要的小伙伴不要错过了。闪耀暖暖五花肉月饼食谱配方一览1月饼关卡自己鬼谷八荒仅剩的3好评,全是三国杀玩家打的?从国产之光到差评如潮,鬼谷八荒做错了什么?文星晖编石灿一匹黑马倒下,一场闹剧开演。2022年4月下旬,曾被许多玩家称赞为国产独立游戏佳作的鬼谷八荒陷入了空前绝后大危机。刺眼的红色差网易宣布永劫无间5月5日前免费玩,包括SteamEpic平台IT之家4月30日消息,永劫无间官方宣布,为迎接五一假期的到来,决定于4月30日00005月4日2400开启限时免费试玩活动,同时开启5。8折扣。IT之家了解到,官网SteamEP红极一时的洗碗机,买了会后悔吗?用了半年多,聊聊使用感受现在越来越多的年轻人坚信科技改变生活,所以生活中的很多家务活都会交给高科技小家电。清洁地面,使用扫地机器人清洁碗筷,使用洗碗机洗衣服,使用洗衣机。这些高科技小家电,时时刻刻在改变着月下天马浪漫你的幸福时光导读千年巴州城,一座天马山。天马山国家森林公园位于大巴山南麓,距离巴中主城区35公里,森林公园总面积2297公顷,森林覆盖率95,海拔1431。6米,负氧离子平均值达到10000个买完就后悔!华强北版AirPods真的不值,还是国产大牌耳机香9月8日凌晨的苹果秋季发布会上,除了全新的iPhone14系列之外,苹果还推出了全新的第二代AirPodsPro。新款的AirPodsPro搭载全新的H2芯片,降噪能力可达上一代A德国晋级!施罗德与妻子相拥,字母弟掩面哭泣,字母击中对手头部男篮欧锦赛14决赛的比赛继续打响,最先进行的一场比赛是西班牙对阵芬兰的比赛,本场比赛芬兰取得了梦幻开局,超快的节奏,完美的转移球,芬兰一度取得了2位数的领先,但是遗憾的是在下半场,有排面!人民日报新节目邀请OPPO做客,绿厂这回又大秀了一手文手机技巧库对于绝大多数人而言,国际品牌一直是很多人最信任的,即便是价格再高,也有人不惜花上重金去购买。而随着国货在质量功能方面上的追赶,也赢取到了不少消费者的信任。不难发现,越来
民间流传一孕傻三年隐藏在背后的最重要原因,你知道吗?我国民间一直流传着一孕傻三年的说法,但是这种说法主要是形容孕妇的智商有所下降,会在平时的表现过程中可能出现丢三落四,对日常能够理解的东西也不能理解的现象。这原本只是一句玩笑话,但是那个不可一世的谢娜,也走到了今天2022年5月,乘风破浪的姐姐第三季在众多关注下开播,因为前两季的良好开端,所以第三季的节目也是备受瞩目。然而让观众们感到意外的是,这一季的节目居然由谢娜来主持,在观众的眼里,她除当你一不小心把男友裸照发给了同事2022高考季漫画来源于费加罗夫人文百芜baiwu每天分享有趣漫画有时候微信聊天框多了,总会发错信息,看着群里半天没有人回应,一打开微信,发现有个开了免打扰的群消息是99,瞬间一种最后的巴菲特午餐返场,谁能获得共进天价午餐的机会?花2000万元吃一顿午餐,有人愿意吗?在一年一度的巴菲特午餐上,这样堪称疯狂的盛事就屡屡上演。6月12日,这一时刻再次到来,而且,这将是巴菲特的最后一次慈善拍卖午餐。谁将在这最后一国足希望不到30!下届扩军世界杯,亚洲10队抢2名额,球迷等不起随着澳大利亚赢得附加赛进入世界杯,2022年世界杯历史性有亚洲6支球队参赛了。平心而论,日韩澳伊沙的地位依然无人动摇,属于在亚洲明显高一档的存在,而卡塔尔拿下亚洲杯冠军后也无限接近带货主播曝光内幕,工资少另有原因?大伙可能有一种印象,带货的主播应该很赚钱。可小郑姑娘反映,5月份,她被一位老板娘请去当带货主播,干了19天,对方只给她转了1680元。视频带货主播干了19天只有1680元,业绩不达2022年高考已落下帷幕,网友热议广东省预估录取分数线高考虽已结束,但考生和家长却不能完全轻松。目前,各省高考阅卷正在紧张而有序地开展,考生和家长都在密切关注阅卷信息。虽然通过对照高考参考答案,大致能估出自己的高考成绩,但在全省处于什扎心!利率房价双站岗!去年最惨买房人哭诉今年真幸福今年以来,佛山楼市的利好一波接一波。在等等派狂欢的背后,隐藏着无数高位站岗人的心酸苦楚。在这里,小编轻轻的问一句1年前勇猛上车,站在山岗上的买房人,你们还好吗?比惨大赛,一个比一个广东奇怪的村,其姓氏在百家姓中找不到,当地人是鲜卑族后裔说到百家姓大家可都听说过,但是真正能把百家姓当中的所有姓氏,背诵出来的人却寥寥无几。每当我们的身边出现了自己以前没有接触过的姓氏的人,就会觉得十分的新奇,觉得还有这样一个姓氏。有的唐山打人陈继志施暴后曾就医,当晚没有被抓起来吗?虽然不懂法律,但是看了视频是个正常人都会知道几个女孩的伤情很重吧,不是一般的重吧,自己的心在滴血,更何况她们自己呢?不敢想舆论不到沸点的时候,是不是施暴者仍然活得好好的,他还要在人6月14日,全球外围市场美股纳指超4,今日大盘预测预计今日会再一次洗盘,早上醒来美股三大指数大跌纳指跌超4。5热门中概股重挫。昨夜,美股三大指数大幅收跌,道指跌2。79,创2021年2月以来新低纳指跌4。68,创2020年10月以