ENTRY组成

HashMap底层原理解析_链表

1.Map数组【put】值

数组如果想要放一个数值, 或者获取一个值,必须根据数组【i】的下标:

当hash.put("1","2")时,决定这个Entry对象位置的是这个 key ,然后根据这个 key 经过 hash 计算产生一个下标 index :关系暂时理解如下图所示

       

{\color{Red} }entry -> key -> hash -> % -> index{\color{Red} }

HashMap底层原理解析_红黑树_03

那么当map中第一次放值后,第二个值相同的index值,怎么放?

JDK1.7头插法

 会为第一个值,增加一个属性 next 指针的属性,设置下一个节点下标

HashMap底层原理解析_链表_04

,其中这个next 代表的是Tablei[ndex],当第二个值put时,会在把这个entry对象的next指向第一个entry;

Map初始化数组

 tableSizeFor的功能(不考虑大于最大容量的情况)是返回大于输入参数且最近的2的整数次幂的数。

HashMap底层原理解析_数组_05

HashMap底层原理解析_红黑树_06

map构造方法初始化容量是16;但是如果 new HashMap(10)时,其实背后默认还是16,但是为什么要这么做?

  1. 图中第一句,初始化数组,通过第一句2的幂指数函数计算后,更改为16;
  2. 第三句就是拿着这个数值给map初始化;
  3. 第二句其实扩容机制的计算(数组的容量*加载因子)【扩容的域值】;
  4. 第四局就是一个hash种子;

问题1:为什么初始化数组时,它的容量一定要是2的幂指函  数? 

问题2: 为什么在计算hashcode值,它要去计算右移和异或运算?

Java1.4版本以后map真实的的计算下标值方式:&

上面用取余来计算下标值,其实是JDK1.4的计算方式,但是效率太慢,所以更新之后实际上是通过 &(与) 操作; 

HashMap底层原理解析_数组_07

问题1:为什么初始化数组时,它的容量一定要是2的幂指函  数? 

如下图所示,

HashMap底层原理解析_红黑树_08

如果length为2的次幂  则length-1 转化为二进制必定是11111……的形式,在于h的二进制与操作效率会非常的快,
而且空间不浪费;如果length不是2的次幂,比如length为15,则length-1为14,对应的二进制为1110,在于h与操作,
最后一位都为0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费 。

map 【put】 的源码

HashMap底层原理解析_数组_09

如图所示,

for循环就是检测,是否有相同的key值,如果有相同的key,就会返回出来,并且覆盖老值;

从图中的 if(e.hash == hash && ((k = e.key ) == key || key.equals(k))) 这句代码就可以看出来:如果hash地址相同,不代表就是一个对象

2.map 扩容

  • JDK1.7版本后,先扩容;
  • JDK1.8版本后,先添加元素在扩容;

不断地put,链表越来越长,为了优化get的效率,只能进行扩容 (其实扩容就是重新生成一个数组) ;

然后原先的ENTRY对象是怎么放到新的数组?

原先是经过计算出来hashcode,然后再根据 hashcode & length -1 计算出来下标;

扩容时:不用计算hashcode 值,只是 hashcode & length -1 计算下标就可以;

如果hashcode值,不重新计算,会不会导致老entry对象还在原来的数组下标下?

其中包含一个规律:

HashMap底层原理解析_数组_10

这样的不会导致所有对象都会加上去么?待解决

3.map 【get】

 1.先去判断是否为空;

 2.然后根据key值经过hashcode计算,算出来下标;

 3.拿着下标遍历这节点,先比较hashcode值, 再比较内容,最后获取出来这个值;

HashMap底层原理解析_数组_11

 4.红黑树

红黑树有两个条件:【数组长度】、【链表长度】

HashMap底层原理解析_红黑树_12

   

   因为不管怎么优化,数组的某个下标位置,也还是会出现链表过长的情况;

   于是就采用红黑树进行优化;

那为什么用红黑树?而不用其他二叉树?

   要考虑到get与set的效率,红黑树是一个折中的结构;

   什么时候由链表转化为红黑树?什么时候红黑树转化为链表?

   当链表的元素>=8,就会改成红黑树,这个8数值是一个开发程序员经验以及实践校验的结果:当>=8时,就会影响到get与set的效率。 

   当红黑树的元素<=6时,就会转化为链表; 

HashMap底层原理解析_链表_13

为什么一个是8,一个是6?不能相同?

 防止某个间隔时间内,你频繁的操作map,造成红黑树与链表反复的切换,这样也会影响性能;

为什么Java1.7版本原来用的是头插法,而在Java1.8后,换成了尾插法?

其实是因为转变红黑树的原因,既然要转变红黑树,那么就得判断这个链表长度,就得需要挨个计数,所以既然要遍历判断是否扩容,所以索性就在遍历过程中,把元素放在了尾部 -> 提高了效率,这是一个其中原因;