总所周知,Java中键值对集合,我们最常用的就是HashMap,那么它的数据结构,以及如何存储键值对,包括为什么使用红黑树,链表等许多数据结构,下面我们一起学习交流
1.HashMap的数据结构:
HashMap的数据结构采用:数组(哈希表)+链表+红黑树
1.数组(哈希表):
HashMap内部定义了一个数组,数组中的每个位置被称为“桶”(Bucket),这是HashMap的基础结构【哈希表】;
下标位置:当添加一个新的key-value 键值对时,会根据key的hashcode(),通过哈希函数(也叫哈希干扰函数)计算出一个新的哈希值 hash,并通过这个 hash值,计算key-value键值对在数组中的下标位置(桶,Bucket)
数组容量:在添加第一个key-value键值对时,数组容量被初始化为16,并且可以根据key-value键值对的数量和负载因子(加载因子),数组会自动按照两倍扩容
2.链表:
数组的每个位置(Bucket 桶)可以保存一个或多个key-value键值对
当两个或更多的key-value键值对,被映射到数组的同一个位置(桶,bucket)时,就产生了哈希冲突
HashMap使用链地址法,解决哈希冲突,这些键值对将以链表的形式存储在产生冲突的位置(桶,Bucket)
3.红黑树:
为了优化链表的查询性能,当链表长度超过一个阈值(默认是8)并且数组的容量大于等于64时,链表会转换成红黑树
红黑树是一种自平衡的二叉查找树,它可以基于二分查找的方式,进行元素的查找,提高查找性能,这对于较长的链表来说是一个明显的性能提升
当红黑树中的节点数量减少到6各或更少时,红黑树转换回链表
.HashMap如何计算key-value键值对元素在数组中的存储位置:
为了时key-value元素可以均匀散列的保存在HashMap的数组中,所以使用key的哈希值进行哈希扰动函数计算出一个新的hash值
在JDK1.8版本之前,HashMap会将这个hash值与数组长度进行%模运算出一个下标,从而确定key-value在数组中的存储位置
例如:数组长度默认为16,12580%16=4,753951%16=15,所以哈希值为12500,753951的键值对,会存储在数组下标为4和3的位置
在JDK1.8版本以后,由于% 模运算性能消耗比较大,所以采用(长度-1)&hash的位运算来计算存储位置
例如:数组长度默认为16,(16-1)&12580=4,(16-1)&753951 ,所以哈希值为12580,753951的键值对,会继续存储在数组下标为4和3的位置。
HashMap的扩容机制?
HashMap的用来进行扩容的方法是resize();
HashMap在以下三种场景下,会触发扩容机制:
1.当HashMap通过无参构造方法创建,在第一次调用put()方法添加key-value键值对时,数组初始化为16;
2.当HashMap中的元素个数超过扩容阈值threshold时,数组的容量按照原容量的2倍进行扩容:
扩容阈值threshold=数组容量×加载因子LoadFactor
加载因子LoadFactor的默认值为0.75,数组容量默认为16,扩容阈值threshold默认值为12(16*0.75=12);
所以,当HashMap中元素个数超过12时,数组的容量按照原容量的2倍进行扩容(16*2=32);
3.加入元素时,如果链表长度大于阈值(默认为8)并且数组长度小于64,会产生数组扩容
HashMap为什么使用链表?
HashMap使用哈希表作为基础数据结构,当两个不同的key-value键值对,通过hash哈希值计算数组下标,出现像疼痛下标情况时,产生哈希冲突
HashMap使用”链地址法“解决哈希冲突,所以需要使用链表,来保存产生哈希冲突的key-value键值对
HashMap为什么使用红黑树?
HashMap中的链表长度增长到一定长度,会导致搜索性能下降(链表是线性方式搜索)
所以,HashMap会在链表过长,将链表转换为红黑树,通过红黑树提供搜索性能(红黑树是二分查找方式搜索)
HashMap不直接使用红黑树的原因:链表简单易于维护,红黑树维护复杂,所以首选使用链表,只有链表长度过长时,才会转换成红黑树来提高搜索查找的性能。
HashMap,LinkedHashMap,TreeMap的区别?
HashMap:无序,基于数组+链表+红黑树实现;
LinkedHashMap:有序,HashMap的子类
TreeMap:自动排序,按照key或者自定义Comparator比较器,进行排序