以下先讲解HashMap
开始先简单总结:
- HashMap在JDK8之前底层:数组 + 链表
- HashMap在JDK8之后底层:数组 + 链表 + 红黑树
在底层存储的时候必定有数组,链表与红黑树是在特定情况下产生的。
1.HashMap底层
1.1Hash底层存储结构
1.首先HashMap中的Key与Value是存储在Node对象中,作为Node对象的属性出现:
2.Node对象是存储在Node数组中:
3.最重要的一点,Node存储在数组中的模式使用的是Set集合的存储思想:无序、不可重复
无序:在存储到
Node[]
数组的时候,并不是顺序存储,而是根据key的hash值计算得来的存储位置。
不可重复:Key是不可重复的,为什么呢?后面会讲。
现在我们知道了:HashMap集合Key与Value存储在对象中,对象存储在数组中
Key、Value --> 对象 --> 数组
如下图:
1.2 HashMap为什么是无序的?不可重复的?
1.向HashMap中添加一组值:map.put(key, value);
- 计算hash值:这时系统会根据key值,调用key所在类的
hashCode()
方法计算hash值
所以为什么存储在map中的对象,需要所在类重写hashCode()方法
- 计算index值:再根据hash值计算index
index = (length - 1) & hash
(length当前底层数组的长度) index就是存储在数组中的位置(下标)。
总结:因为Node对象存储在数组中的位置是根据Key值计算得来,所以并不是顺序存储进去的(所以是无序的),同时因为相同的Key计算出来的index也是相同的,当Key相同时会用当前node替换原始的node,但是还是保持一个map中key值是唯一的(不可重复性)体现在下面源码中:
其中 p 是新增的 node,e是原始的 node:如果hash值相同,key相同,就用新增的替换原来的
我们看到还调用了
key.equals(k)
, 前面的 k == key 判断地址,后面 equals 判断内容,保持绝对相等
所以我们除了为什么无序、不可重复,还得出结论:存储在HashMap中的对象需要重写
hashCode()
与equals()
这两个方法
为什么要使用hash值计算index进行无序存储呢?当我们查找一个key对应的value的时候,就不需要顺序遍历底层数组了,直接计算index值,去对应位置拿就行。比list查找效率大大提升。而且map集合设计之初就是用来根据key取value值的存储结构,需要承担大量查找操作。
1.3 链表与红黑树是如何产生的
1.当我们添加一组值的时候,map.put(key, value);
通过key的hash值计算得到的index,拿着index去数组中存储的时候发现该位置已经有数据了怎么办?
2.那么就会先比较 hash 值,再比较 key 地址值,最后比较key的内容是否相等,如果还是相等呢?那么node中的next属性就派上用场了:
3.系统就会将当前对象node赋值给next,让它链接到原始node的后面(jdk8之后是新node链接到原始的后面,jdk8之前是把新的对象放在数组中,把原始对象链接到新node的后面,总之都是形成一个单向链表)就形成了:数组 + 链表:
4.什么时候会形成红黑树呢?提到下面两个关键的变量:
5.当链表长度达到8,且数组长度达到64时候就会转换为红黑树进行存储。如果链表长度达到8,数组长度还未小于64,就会先进性扩容。
HashMap的每一次扩容都会重新计算每个node的index,因为
index = (length - 1) & hash
,index与数组长度有关。这样也能减少链表与红黑树的出现。
6.所以后续就有可能形成:数组+链表+红黑树 的情况(也不排除偶然:每个计算的index都不相同,只有数组,那就太爽了):
1.4 HashMap何时会进行扩容?
List 默认底层默认是长度为10的数组,存满了(进来第11个元素),就扩容。
HashMap 底层默认是长度为16的数组,加载因子是0.75,也就是说存储超过数组长度在75%也就第一次扩容是12个的时候就会进行扩容。
为什么不存储满了再扩容呢?因为存储位置是计算出来的,万一计算出来的index就是不会出现2,那么2这个位置永远不会存储元素。那就完蛋。
同时,如上面描述的也会在长度未达到64,链表长度就达到8的时候扩容。
现在我们就基本讲完了HashMap的底层了。
2.LinkedHashMap
其实也就是在HashMap的基础上增加了双向指针,指向前一个,后一个,方便遍历。
3.HashSet
为什么放在最后讲,因为Set集合的底层就是调用了HashMap:
创建的时候构造器new了一个HashMap:
添加的时候调用HashMap的put方法:
只不过在一开始new了一个单例的Object作为value:
4.LinkedHashSet
实际上LinkedHashSet底层就是一个LinkedHashMap
LinkedHashSet构造器调用了HashSet中的构造器
HashSet中的这个构造器创建LinkedHashMap对象返回。
总结:
HashMap
默认初始化数组长度:16
扩容倍数:2倍
HashMap的默认加载因子:0.75
Bucket中链表长度大于该默认值,转化为红黑树 : 8
桶中的Node被树化时最小的hash表容量:64
HashMap 是大哥
LinkedHashMap 就是在 HashMap 基础上增加双向链表
HashSet 底层就是 HashMap
LinkedHashSet 底层就是 LinkedHashMap