HashMap底层源码解析上(超详细图解+面试题)

红黑树原理+手写红黑树代码
HashMap底层源码解析下


最近看了黑马和刘老师的源码视频,故总结一篇HashMap的源码解析,文章分为上下两部分

HashMap集合简介

HashMap基于哈希表的Map接口实现,是以key-value存储形式存在,即主要用来存放键值对。HashMap的实现是不同步的,这意味着他不是线程安全的,它的key,value都可以为null

此外,HashMap中的映射不是有序的

  1. JDK1.8之前HashMap由数组+链表组成。数组是HshMap的主体,链表则是主要为了解决哈希冲突(两个对象调用的hashCode方法计算的哈希值一致导致计算的数组索引值相同(哈希寻址算法[hash&length-1]))而存在的(“拉链法”解决冲突)
  2. JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(或者红黑树的边界值,默认为8,且数组中桶的数量大于64,此时此索引位置上的所有数据改为使用红黑树存储)

图示:
在这里插入图片描述

hashMap扩容简介

  1. 初始数组长度为16,若链表节点数量小于等于8时,会将节点连接到链表的下一个位置
  2. 若某个链表上阈值大于8时,会根据cap(容量)进行判断,若此时数组容量小于64,则会进行数组扩容(从32扩容为64)
  3. 若链表节阈值大于8,且数组容量大于64,则链表进化为红黑树

问题:为什么要在数组长度大于64之后,链表才会进化为红黑树?

  • 在数组比较小时如果出现红黑树结构,反而会降低效率,因为红黑树需要进行左旋右旋,变色这些操作来保持平衡,同时数组长度小于64时,搜索时间相对要快些,所以综上所述为了提高性能和减少搜索时间。

hashMap特点总结

  • hashMap存取是无序
  • 键和值位置都可以是null,但是键位置只能是一个null
  • 键位置是唯一的,底层的数据结构是控制键的
  • jdk1.8前数据结构是:链表+数组jdk1.8之后是:数组+链表+红黑树
  • 阈值(边界值)>8并且数组长度大于64,才将链表转换成红黑树,变成红黑手的目的是为了高效的查询’

hashMap底层数据结构存储数据的过程

在了解源码之前,我们通过一个案例来说明hashMap底层数据结构存储数据的过程

我们先创建一个hashMap实例

        HashMap<String, Integer> MapTest = new HashMap<>();
        MapTest.put("a",1);
        MapTest.put("b",2);
        MapTest.put("c",3);
        MapTest.put("a",44);
        System.out.println(MapTest);

结果:

{
   
   a=44, b=2, c=3}

存储过程分析

HashMap<String, Integer> MapTest = new HashMap<>();

1.在创建集合对象的时候,在jdk8前,在构造方法中创建一个长度为16的Entry[] table数组用来存储键值对数据,在jdk8以后不是在HashMap构造方法底层创建数组了,是在第一次调用put方法时创建的数组Node[] table

不在构造方法中创建的原因是:创建散列表需要耗费内存,而有些时候我们只是创建hashMap,并不向其中put元素

2.假设向哈希表中存储“a”-1数据,根据"a"调用String类中重写之后的hashCode方法计算出hash值,利用寻址算法(hash&(cap-1))cap为其容量,来寻找在哈希表中的索引,如果该索引空间没有数据,则直接将数据存储到数组中

在这里插入图片描述
3.向哈希表中存储数据"b"-2,假设"b"计算出的hashCode方法结合数组长度计算出的索引值也是3,如果此时数组空间不是null,此时底层会比较a和b的hash值是否一致,如果不一致,则在此空间上划出一个节点来存储键值对数据“b”-2,这种方式称为“拉链法”
在这里插入图片描述
4.假设向哈希表中存储数据"a"-44,根据“a”调用hashCode方法结合数组长度计算出索引值为3,此时数组空间不为null,此时比较后存储的数据留言和已经存在的数据的hash值是否相等,如果相等,此时发生hash碰撞,那么底层会调用“a”所属类String中的equals方法比较两个内容是否相等

  1. 相等:则将后添加的数据的value覆盖之前的value
  2. 不相等,此时出现hash冲突,继续向下和其他的数据key比较,如果都不相等,则划出一个节点存储数据

相同hash值的元素内容可能不同,例如"重地"和"通话"

System.out.println("重地".hashCode()=="通话".hashCode());
true

索引值->hash值->key值

为什么引入红黑树的进一步解答

JDK1.8以前HashMap的实现是数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当HashMap中有大量的元素都存放在同一个桶中时,这个桶下有一条长长的链表,此时HashMap就相当于一个单链表,假如单链表有n个元素,遍历的时间复杂度就从O(1)退化成O(n),完全失去了它的优势,针对此种情况,JDK1.8中引入了红黑树(查找的时间复杂度为O(logn))来优化这种问题。

总结图

在这里插入图片描述
说明:

  • size表示HashMap中K-V的实时数量,注意这个不等于数组的长度
  • threshold(临界值)=capacity(容量)*loadFactory(加载因子),这个值是当前已占用数组长度的最大值。size超过这个临界值就重新resize扩容,扩容后的HashMap容量是之前容量的两倍

了解了hashMap的存储流程之后我们来看几道面试题加深印象

评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温文艾尔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值