HashMap源码解读

514 阅读3分钟

前言

HashMap作为面试中经常被问到的数据结构,而且我们平时用到的也非常多,那么今天就来盘一盘HashMap的源码。结合平时背的关于HashMap的八股文来看一看他底层到底的怎么实现的,这样以后再面对面试官的夺命连环问也能应付自如了不是。

本文是基于java8的HashMap分析。

构造函数

图片.png 有4个构造函数,其中无参构造是给loadFactor赋一个默认值。这个loadFactor就是负载因子。默认0.75;

图片.png 我们也可以自己指定负载因子数,但是我们指定的负载因子必须大于0。

图片.png

注释上写的明明白白,这个initialCapacity是初始化的大小。

我们知道HashMap底层是数组+链表+红黑树(jdk8),以K,V的形式存在,那他刚创建的数组的大小就是可以通过initialCapacity这个参数来指定,默认是16。

那么问题来了。

面试官:他是在new HashMap的时候就给初始化数组大小的么?

我:额。。。是啊。

面试官:指定初始容量创建的时候是指定多少就是多少么?比如指定容量大小为17,那他创建的数组大小是17么?

我:额。。。不是么?

面试官:。。。。。

图片.png

话不多说,上代码:

图片.png 这里好像并没有数组什么事,只是把初始容量经过tableSizeFor后赋给了threshold,这个tableSizeFor方法等会再看。

那我们再看数组上面的注释。

图片.png

在第一次put的时候才去初始化大小!!

而在指定初始容量大小的创建的时候只是把阈值大小给指定了。看代码:

图片.png

图片.png

我们再回头看这个给threshold赋值的方法:tableSizeFor这个方法:

图片.png

图片.png

根据注释,我们看出:他是获取目标值的最小二次幂。

那么这两个问题的答案就显而易见了, 首先,他是在第一次put的时候才去进行数组的初始化。

然后他初始化的大小是传入值的最小二次幂,比如指定是8 那么初始化大小为8。指定是15 初始化大小为16。

数组+链表+红黑树

HashMap经过1.8版本优化之后他是把链表的头插法改成的尾插法,链表到达一定条件后转为红黑数的数据结构。那我们来看下这两个步骤是在什么地方进行的吧。

图片.png

图片.png 我们再看这个转红黑树的方法:他是直接就把链表转成红黑树吗?

图片.png 他是进行了一次判断:如果数组的长度大于64的时候才去转红黑树的,否则把原来的数据进行一次resize();

那么我们就得出结论:链表在长度大于8的时候,数组的长度大于64的时候才会转为红黑树的数据结构。

经过我们一顿分析,可以画出HashMap的数据结构:

图片.png

扩容

我们知道HashMap扩容是每次扩容为原来的两倍,那他是怎么进行扩容的呢?答案就再resize()方法里面

图片.png

这时候面试官又问了:

面试官:扩容的时候旧数据进行rehash之后不是和原来的hash值一样吗,还是会造成hash冲突,HashMap是怎么保证resize之后数据均匀的分布呢?

你:。。。。

看代码:

图片.png

圈起来的地方就是答案所在。把hash值和长度进行逻辑与处理后再放到新数组,这样就可以保证旧数据再新的数组里面均匀分布啦。

经过文本的分析,想必各位靓仔对HashMap的理解已经臻至化境。下次再遇到关于HashMap的面试题那不是随便拿捏。

图片.png

最后说一句

才疏学浅,难免会有纰漏,如果文章有不对的地方欢迎大家指正,一起进步。