Java集合详解——HashMap类(包括 底层机制源代码分析)

目录

1. 基本介绍

2. HashMap类 底层机制分析

2.1 示意图

2.2 扩容机制(和HashSet相同)

3. HashMap类 底层机制 源代码分析

[第一次添加:put("java", 10)]

[第二次添加:put("php", 10)]

[第三次添加:put("java", 20)]


1. 基本介绍

(1)Map接口的常用实现子类:HashMap、Hashtable和Properties。

(2)HashMap是Map接口 使用频率最高的实现类。

(3)HashMap是以key-value对的方式 来存储数据。

(4)key不能重复,但是是值可以重复,允许使用null键和null值。

(5)如果添加相同的key,则会覆盖原来的key-value,相当于修改(key不会替换,value会替换)。

(6)与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的。

(7)HashMap没有实现同步,因此是线程不安全的。(方法没有做同步互斥的操作,没有synchronized修饰)

2. HashMap类 底层机制分析

2.1 示意图

①k-v是一个Node 实现了Map.Entry<K,V>,查看HashMap的源码可以看到。

②JDK 7.0的HashMap 底层实现(数组+链表),JDK 8.0底层实现(数组+链表+红黑树)。

2.2 扩容机制(和HashSet相同)

①HashMap底层维护了Node类型的数组table,默认为null。

②当创建对象时,将加载因子(loadfactor)初始化为0.75。

③当添加key-value时,通过key的hash值得到在table数组的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断 该元素的key 和 准备加入的key 是否相等,如果相等,则直接替换value。如果不相等,需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。

④第1次添加,则需要扩容table容量为16,临界值(threshold)为12。

⑤以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推。

⑥在JDK 8中,如果一条链表的元素个数达到 TREEIFY_THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64)就会进行树化(红黑树)。

3. HashMap类 底层机制 源代码分析

(1)执行new HashMap()后,调用HashMap的无参构造器,初始化加载因子loadFactor为0.75。执行完这条代码后,此时HashMap$Node[] table = null。

[第一次添加:put("java", 10)]

(2)执行put("java", 10)后,先调用valueOf()方法,对基本数据类型进行自动装箱操作。

(3)装箱完成后,就调用并执行put(K key, V value)方法。在该方法中会调用hash()方法,通过一个算法(不用深究),来计算出key的hash值。

(4)在得到key的hash值后,就进入到了putVal()方法。

//这个putVal()方法是和HashSet底层进入的同一个putVal()方法,每条代码的详细解读 去“HashSet底层机制 源代码分析”查看

(5)在putVal()方法中,进行第一个if语句的判断。首先判断table数组是否为null。

此时的table数组就是null,判断成功后,就进入if语句,调用resize()方法,对table数组扩容为16个空间,每个空间的值都是null。

//在resize()方法中,扩容的核心语句就是下面这句

(6)扩容后,就进行第二个if语句的判断。通过hash值得到一个table数组的索引位置的Node节点,判断这个节点是否为null。

此时因为整个table数组都是null,所以得到的Node肯定为空,就判断成功,进入if语句,创建一个newNode新的节点,将加入的key-value值放到对应的节点。

//执行完添加后,此时table数组中,索引为3的位置就有了新的节点

(7)执行完第二个if语句后,就不进入下面的else语句,直接来到了最后。执行++modCount语句后,修改次数加1。

然后if语句判断数组的元素个数是否超过threshold。此时数组元素只有一个,没有超过12个,不进入if语句,就在最后返回null,完成第一对key-value添加。

[第二次添加:put("php", 10)]

(8)执行put("php", 10)操作,进行第二对key-value添加。依然是先调用valueOf()方法进行自动装箱。然后调用put()方法,再执行hash(key)方法,计算key的hash值。最后进入putVal()方法。

(9)在putVal()方法中,进行第一个if语句的判断。因为此时table数组中已经有元素了,所以不为null,不会进入这个if语句。

(10)然后进行第二个if语句的判断。此时的hash值与第一次的不同,所以得到的table数组的索引位置的Node节点也不同,就应该还是null。进入这个if语句,创建一个newNode,将新的key-value值放到对应的节点。

//执行完添加后,此时table数组中,索引为9的位置就有了新的节点

(7)执行完第二个if语句后,同样不进入下面的else语句,直接来到了++modCount语句,修改次数加1。数组元素只有两个,没有超过12个,不进入if语句,就在最后返回null,完成第二对key-value添加。

[第三次添加:put("java", 20)]

(8)执行put("java", 20)操作,进行第三对key-value添加。依然是先调用valueOf()方法进行自动装箱。然后调用put()方法,再执行hash(key)方法,计算key的hash值。最后进入putVal()方法。

(9)在putVal()方法中,第一个if语句判断同样因为table数组中已经有元素了,不为null,不会进入这个if语句。

(10)然后进行第二个if语句的判断。此时加入的key是java,和第一次相同,所以计算出的hash值与第一次的相同。经过算法得到的索引位置也是第一次的位置,所以不为null,就不会进入这个if语句,而是进入else语句。

(11)在else语句中先进行第一个if语句进行判断。此时加入的元素的hash值和 数组中当前p指向位置的hash值相同,同时此时key的内容和数组中指向的内容都是java,所以就满足了这个if语句的判断,执行e = p,使e指向数组中的当前元素。

(12)在else语句中第一个if语句判断成功后,就不执行下面的else if和else语句,而执行第二个if语句判断。此时的e指向了数组中的当前元素,不为null,判断成功就进入这个if语句。

然后在里面的if语句也判断成功,执行e.value = value,将数组中当前指向元素的value = 10替换为此时加入的value = 20。执行完后就返回oldValue,完成第三次添加操作。

(13)三次添加都执行完后,此时的table数组中只有两对k-v,但第一对k-v的value值被替换为了第三次添加的value值。

  • 35
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值