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

java集合HashMap源码解析(基于JDK1。8)

  一、Hashmap简介
  类继承关系图如下:
  HashMap实现了三个接口,一个抽象类。主要的方法都在Map接口中,AbstractMap抽象类实现了Map方法中的公共方法,例如:size(),containsKey(),clear()等,主要方法由子类自己实现。
  HashMap结构如下图:
  HashMap的主要结构由数组、链表/红黑树组成,当数组中某个节点大于等于8个并且数组长度大于等于64时,链表会转换为红黑树。 二、HashMap的主要属性public class HashMap extends AbstractMap     implements Map, Cloneable, Serializable {      private static final long serialVersionUID = 362498820763181265L;          /* ---------------- 默认值 -------------- */          /**      * 默认初始化大小,必须是二的次幂      */     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16      /**      * 数组最大值,当大于该值时使用该值      */     static final int MAXIMUM_CAPACITY = 1 << 30;      /**      * 默认负载因子      */     static final float DEFAULT_LOAD_FACTOR = 0.75f;      /**      * 某个节点由链表转为红黑树时候的阈值      */     static final int TREEIFY_THRESHOLD = 8;      /**      * 节点由红黑树转为链表时的阈值      */     static final int UNTREEIFY_THRESHOLD = 6;      /**      * 节点转为红黑树时数组的阈值      */     static final int MIN_TREEIFY_CAPACITY = 64;           /* ---------------- 字段 -------------- */          /**      * HashMap的数组(划重点,主要结构)      * HashMap的由数组+链表/红黑树组成,数组指的就是这个数组,链表和红黑树则是由Node结构组成      */     transient Node[] table;      /**      * 保存缓存的set.AbstractMap字段用于实现keySet()和values()。      */     transient Set> entrySet;      /**      * HashMap中数据量大小.      */     transient int size;      /**      * HashMap的修改次数      */     transient int modCount;      /**      * 阈值,当HashMap中数据大于该值时将进行扩容      */     int threshold;      /**      * 加载因子      */     final float loadFactor;               /* ---------------- Node结构 -------------- */         /**      * HashMap的基础节点      */     static class Node implements Map.Entry {         //当前节点的hash值         final int hash;         //当前节点的key         final K key;         //当前节点的value         V value;         //指向下一个节点的引用         Node next;          //node的构造函数         Node(int hash, K key, V value, Node next) {             this.hash = hash;             this.key = key;             this.value = value;             this.next = next;         }         //...其他方法省略     }       复制代码三、源码解析1、初始化方法
  HashMap共有四个构造函数,参数分别是1、初始化数组大小、加载因子。2、初始化数组大小。3、无参。4、HashMap结构。
  其中1、2、3三个参数的构造函数性质相同,都是传入初始化数组大小或加载因子,没传的使用默认值。构造函数4使用默认的初始化大小和加载因子,并且是将传入的HashMap添加到新的结构中。
  具体代码如下:/**  * 有初始化大小和加载因子大小的构造函数  * @param  initialCapacity 初始化大小  * @param  loadFactor      加载因子  * @throws IllegalArgumentException 非法参数异常  */ public HashMap(int initialCapacity, float loadFactor) {     //如果初始化大小小于0,抛出异常     if (initialCapacity < 0)         throw new IllegalArgumentException("Illegal initial capacity: " +                                            initialCapacity);     //如果初始化大小大于最大值,那么就把初始化大小设置为最大值     if (initialCapacity > MAXIMUM_CAPACITY)         initialCapacity = MAXIMUM_CAPACITY;     //如果加载因子小于等于0或者是非法的float类型,则抛出异常     if (loadFactor <= 0 || Float.isNaN(loadFactor))         throw new IllegalArgumentException("Illegal load factor: " +                                            loadFactor);     //设置加载因子     this.loadFactor = loadFactor;     //设置下一次扩容阈值     this.threshold = tableSizeFor(initialCapacity); }  /**  * 有初始化大小的构造函数 加载因子使用默认值(0.75)  *  * @param  初始化大小  * @throws IllegalArgumentException 非法参数异常  */ public HashMap(int initialCapacity) {     //调用第一个构造函数,加载因子使用默认值DEFAULT_LOAD_FACTOR(0.75)     this(initialCapacity, DEFAULT_LOAD_FACTOR); }  /**  * 无参构造函数,使用默认大小(16)和默认加载因子(0.75)  */ public HashMap() {     //设计加载因子为默认值,其他所有值都是要默认值     this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }  /**  * 使用一个HashMap为参数的构造函数,加载因子使用默认(0.75)  *  * @param   一个HashMap数据  * @throws  NullPointerException 空指针异常  */ public HashMap(Map<? extends K, ? extends V> m) {     //加载因子为默认值     this.loadFactor = DEFAULT_LOAD_FACTOR;     //将传入的HashMap数据放入当前结构中     putMapEntries(m, false); } 复制代码2、get方法
  先看源码,再做总结,源码:/**  * HashMap的get方法  * @param   要查找的key  */ public V get(Object key) {     //定义一个node     Node e;     //通过getNode()方法获取node,getNode返回null则get方法返回null,否则返回node的value     return (e = getNode(hash(key), key)) == null ? null : e.value; }  /**  * 实现Map.get和相关方法  * @param key的hash值  * @param key  * @return 结构中的node或者null  */ final Node getNode(int hash, Object key) {     //定义说明  tab:数组,first:该数组节点中的第一个值,n:数组大小,k:first的key     Node[] tab; Node first, e; int n; K k;     //如果数组不为null、数组大小大于0、通过hash获取到的数组中的节点不为null     if ((tab = table) != null && (n = tab.length) > 0 &&         (first = tab[(n - 1) & hash]) != null) {         //该节点的hash等于要查找的hash值(始终检查第一个节点)、该节点的key与要查找的key相等(==为true或者equals为true)         if (first.hash == hash && // always check first node             ((k = first.key) == key || (key != null && key.equals(k))))             return first;         //如果第first节点不为null并且first节点不是要查找的节点(上面的if判断,如果是要查找的接口则上一步就返回了)         if ((e = first.next) != null) {             //如果是红黑树类型             if (first instanceof TreeNode)                 //遍历红黑树                 return ((TreeNode)first).getTreeNode(hash, key);             //循环遍历链表             do {                 //当hash值相同、该节点的key与要查找的key相等(==为true或者equals为true)                 if (e.hash == hash &&                     ((k = e.key) == key || (key != null && key.equals(k))))                     return e;             } while ((e = e.next) != null);         }     }     //如果hash值对应的数组为null,则返回null     return null; }  复制代码
  get方法总结:根据key的hash值找到数组中对应的位置。判断该位置上的值和要查找的值是否相等(==或者equals),如果是则返回如果不是则判断该节点的下一个节点是否为空,为空则返回null。判断结构是否是红黑树,如果是,遍历树。如果不是树,则遍历链表。如果不符合上面的条件则返回null。3、put方法
  先看源码:/**  * 添加key-value,如果key已经对应value,则替换,返回之前的值  *  * @param key  * @param value  * @return 返回之前的value  */ public V put(K key, V value) {     return putVal(hash(key), key, value, false, true); }  /**  * 实现Map.put和相关方法  *  * @param hash值  * @param key  * @param value  * @param onlyIfAbsent 是否只在不存在的时候修改值,true不修改,false修改  * @param evict 如果为false,则为创建模式  * @return 返回之前的value,如果没有则为null  */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                boolean evict) {     //变量说明tab:当前数组,p:当前节点,n:数组大小,i:要插入的数据在数组中的位置     Node[] tab; Node p; int n, i;     //数组为空或者数组大小为0 初始化数组(resize()扩容函数,也包括初始化数组,后面扩容会分析)     if ((tab = table) == null || (n = tab.length) == 0)         n = (tab = resize()).length;     //如果对应数组中的位置为null,将当前数据构造成Node放入该节点     if ((p = tab[i = (n - 1) & hash]) == null)         tab[i] = newNode(hash, key, value, null);     else {         Node e; K k;         //当hash值相同、该节点的key与要插入的key相等(==为true或者equals为true),则替换该value         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) {                 //如果没有遍历到与该key相同的数据,则在链表最后添加该数据节点                 if ((e = p.next) == null) {                     p.next = newNode(hash, key, value, null);                     //如果该链表长度大于等于8,则将链表转换为树                     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             V oldValue = e.value;             if (!onlyIfAbsent || oldValue == null)                 e.value = value;             //LinkedHashMap使用,HashMap中方法体为空             afterNodeAccess(e);             return oldValue;         }     }     ++modCount;     //如果数组中的值大于阈值,则扩容     if (++size > threshold)         resize();     afterNodeInsertion(evict);     return null; } 复制代码
  put方法总结:判断HashMap中的数组是否为空或者大小为0,如果是则初始化数组。如果该hash值对应的数组中的位置为空,则将该数据组成的节点直接插入到该位置中。如果数组对应位置数据不为空,判断该位置节点的key与要插入的key是否相等,如果是设置e(局部变量)等于该节点。如果Node结构为树结构,则遍历树结构找到key对应的节点,设置为e。如果Node结构为链表,遍历链表,如果链表中没有找到对应的key,将数据构造成Node节点插入链表最后,如果链表长度大于等于8,则将结构转为树。如果在链表中找到对应的key,则将该节点设置为e.如果e(上面遍历找到的节点)不为null,则设置新的value,返回旧的value.如果e为null,说明是新增,HashMap大小加一,判断是否大于阈值,如果大于,则扩容。4、扩容
  先看源码:/**  * 初始化或者扩容  *  * @return the table  */ final Node[] resize() {     //旧的数组     Node[] oldTab = table;     //旧的数组大小     int oldCap = (oldTab == null) ? 0 : oldTab.length;     //旧的阈值     int oldThr = threshold;     //新的数组大小和阈值     int newCap, newThr = 0;     //如果旧的数组大小大于0(只要初始化过就都会大于0)     if (oldCap > 0) {         //旧的数组大小已经达到最大,那么设置阈值为最大值         if (oldCap >= MAXIMUM_CAPACITY) {             threshold = Integer.MAX_VALUE;             return oldTab;         }         //如果旧的数组扩大两倍小鱼最大值并且旧的数组大于等于初始化值,那么设置新的阈值为旧的阈值的两倍         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&                  oldCap >= DEFAULT_INITIAL_CAPACITY)             newThr = oldThr << 1; // double threshold     }     //如果数组大小为0,阈值大小大于0,则设置新的初始化大小为阈值,否则全部使用默认值     else if (oldThr > 0) // 初始容量设置为阈值         newCap = oldThr;     else {               // zero initial threshold signifies using defaults         newCap = DEFAULT_INITIAL_CAPACITY;         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);     }     //如果新的阈值等于0,那么设置新的阈值为新的数组大小*加载因子     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;     //旧的数组不为null,说明不是初始化,需要扩容     if (oldTab != null) {         //遍历旧得数组         for (int j = 0; j < oldCap; ++j) {             Node e;             //如果该位置的节点不为null,那么遍历链表或者树放入新的数组中             if ((e = oldTab[j]) != null) {                 oldTab[j] = null;                 //如果只有一个节点,直接放入新数组中对应的位置                 if (e.next == null)                     newTab[e.hash & (newCap - 1)] = e;                 //如果是树结构,拆分树                 else if (e instanceof TreeNode)                     ((TreeNode)e).split(this, newTab, j, oldCap);                 //链表结构,拆分链表                 else { // 保持之前的顺序                     //低位头、尾节点                     Node loHead = null, loTail = null;                     //高位头、尾节点                     Node hiHead = null, hiTail = null;                     Node next;                     do {                         next = e.next;                         //如果hash值&旧的数组大小为0,说明放到新数组后还是之前的位置,否则为(当前位置+旧数组大小)的位置                         //遍历第一个节点时,头、尾都设置为该节点,之后的节点添加到该节点之后,并设置尾节点为后添加的节点                         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) {                         loTail.next = null;                         newTab[j] = loHead;                     }                     if (hiTail != null) {                         hiTail.next = null;                         newTab[j + oldCap] = hiHead;                     }                 }             }         }     }     return newTab; } 复制代码
  扩容方法总结:数组是否已经达到最大值,如果已经最大,设置阈值也为最大值,否则数组大小和阈值都改为之前的两倍。数组是否已经初始化,如果没有则初始化数组和阈值。旧数组不为null,遍历旧数组,将对应位置的链表/树分为成两个链表/数组,一个在原先的位置上,一个在原先的位置+原先数组大小的位置上,将两个链表/树放入新数组的对应位置。

95后和00后的差距,有多大呢?网友真的是差了亿点点!(漫画)现在95后00后的年轻人生活在新时代,享受到了国家经济飞速发展带来的优越生活,所以思想和生活习惯都比较自由。现在95后00后的年轻人,平时都喜欢一些比较时尚的东西,比如衣服食物以及张靓颖胖了也没影响颜值,穿卫衣化浓妆不好惹,但笑起来还好看春日生活打卡季今天穿什么喜欢穿卫衣的女孩子,从来都不会觉得这是一种很多余的服装,相反会觉得它是很有必要的存在,在那些不知道穿什么的日子里,在换季的时候,甚至是在平时的打扮中,都可以夏天的时髦,离不开T恤纯色T恤,一早被我们贴上了普通的标签,它总是作为基础款被淹没,和高级没有任何关系,出现在极为日常的时候,甚至很多人都不愿单穿出门。其实啊,T恤的气质又岂止一面?它除了能带来放松舒适Kedall仅用一条基础款牛仔裤告诉你,怎么穿显高显瘦又时髦大家好,欢迎来到我的快乐搭配频道导语人人都爱的牛仔裤,并不一定人人都能穿得好看又显身材。作为牛仔裤的铁粉,我也曾经尝试过各种类型。包括各种超个性的印花款式带有铁链或者金属环的类型等博格斯身高只有1。6米,为什么他能成为NBA历史上了不起的球星?提到NBA,相信大家都有很多话题,这里聚集世界最好的篮球运动员,他们来自世界各地,当然来自美国最多,在我们印象中的巨星如乔丹科比詹姆斯奥尼尔姚明等人,当然这些巨星的成长离不开他们先原来都是伪球星首轮比赛尚未结束,联盟已有9大球星被打回原形声明文章数据为北京时间4月27日前数据NBA季后赛正进行得如火如荼,结果也是有人欢喜有人忧。北京时间4月26日,篮网主场112116不敌凯尔特人,总比分04惨遭淘汰,成为本赛季季后折叠屏手机速览华为MateXs2发布三星新机定档小米原型机曝光4月28日,有三条折叠屏手机的消息。在刚刚结束的新品发布会上,华为发布了旗下第5块折叠屏手机MateXs2,售价9999元起。MateXs2采用外折设计与双旋鹰翼铰链技术,搭载7。安卓之光小米11Ultra降价后狂卖四万台,是真香机吗2021年3月份小米发布了小米11Ultra。首发标准版5999。时隔一年出头的手机降价到3999,却被狂卖四万台,(根据官方数据,加上第三方售出的应该不止四万台。)发布之时就被誉新品智慧屏VPro发布,华为游戏中心软硬协同推动大屏娱乐体验升级文丨壹观察宿艺客厅是家庭成员休息社交或娱乐时的多元空间,也是未来家庭生活和娱乐的巨大入口,在疫情持续冲击新技术变革与用户需求升级等因素共同加持下,家庭娱乐消费正迎来巨大的风口变革周雷军良心了,天玑90002K120W售价亲民,成小米新一代守门员说到小米手机,一直走的是亲民的路线。不过现在的小米开始冲击高端市场,也让很多消费者表示,小米在性价比方面也有所阉割了。习惯了小米手机的亲民,突然定价较高,也让很多消费者有点不适应,租赁只需49美元!苹果正式推出自助维修服务去年,苹果宣布了其自助维修计划,该计划为iPhone12和13系列以及更新的iPhoneSE2022带来了200多种原装苹果零件和维修工具。该计划现已在美国推出,因此美国的用户可以
83键布局,自润G黄Pro轴米物ART系列Z830什么样的一款数码产品,能深得年轻人喜爱,不论年龄,不论职业,以及实用的方式,如办公,娱乐,甚至电竞竞技都会人手配备的一个的数码产品,我相信说到这里,你会有答案,那就是近年机械键盘。精致时尚带来腕上新体验小米手表S1Pro评测随着小米手表S1的发布,小米开始布局智能手表的高端市场,今天小米手表S1Pro正式亮相,这款智能手表延续了S1的高端定位,并且在设计方面将经典腕表的风格与科技智能相结合,让科技更有83键布局,自润G黄Pro轴米物ART系列Z830什么样的一款数码产品,能深得年轻人喜爱,不论年龄,不论职业,以及实用的方式,如办公,娱乐,甚至电竞竞技都会人手配备的一个的数码产品,我相信说到这里,你会有答案,那就是近年机械键盘。精致时尚带来腕上新体验小米手表S1Pro评测随着小米手表S1的发布,小米开始布局智能手表的高端市场,今天小米手表S1Pro正式亮相,这款智能手表延续了S1的高端定位,并且在设计方面将经典腕表的风格与科技智能相结合,让科技更有经典仙侠古剑奇谭顺应天命和逆天改命,你会怎么选?作者WE古剑奇谭虽然画质不算精美,特效不算绝伦,但它凭借优质的配音和令人深思的剧情依旧赢得很多玩家的称赞。对于喜欢古风仙侠回合制游戏的朋友而言,这是不可缺席的作品。沉溺大作已久,再这是一款经典的1。76正常服传奇传奇作为一款经典的老游戏,从01就开始测试运营,当时还是按照时长收费的,三十多一张的点卡,才能在传奇里面驰骋一个月,不知道多少版本更新过,推出过多少版本,从最早的1。10开始到1。经典仙侠古剑奇谭顺应天命和逆天改命,你会怎么选?作者WE古剑奇谭虽然画质不算精美,特效不算绝伦,但它凭借优质的配音和令人深思的剧情依旧赢得很多玩家的称赞。对于喜欢古风仙侠回合制游戏的朋友而言,这是不可缺席的作品。沉溺大作已久,再这是一款经典的1。76正常服传奇传奇作为一款经典的老游戏,从01就开始测试运营,当时还是按照时长收费的,三十多一张的点卡,才能在传奇里面驰骋一个月,不知道多少版本更新过,推出过多少版本,从最早的1。10开始到1。外国球员没空参加自己的婚礼,让兄弟代替自己当新郎,新娘懵圈了塞拉利昂足球明星布亚图雷(BuyaTuray),今年27岁,司职前锋。上赛季,图雷效力于中超河南嵩山龙门。今年7月22日,他转会瑞典俱乐部马尔默,正式在新球队亮相。不过,问题来了,外国球员没空参加自己的婚礼,让兄弟代替自己当新郎,新娘懵圈了塞拉利昂足球明星布亚图雷(BuyaTuray),今年27岁,司职前锋。上赛季,图雷效力于中超河南嵩山龙门。今年7月22日,他转会瑞典俱乐部马尔默,正式在新球队亮相。不过,问题来了,12连败!中超新耻辱纪录诞生,球员踢养生足球,无视队友怒斥如果有人问哪支球队在本赛季的中超联赛表现最差?相信大家的答案会非常统一,那就是广州城。中超12轮过后,广州城排名联赛积分榜的倒数第一,他们与河北队同样都只取得了3分。但不同的是,河