概述
这是一个针对1到3年左右Java开发人员的面试题,
问题本身不是很难,但是对于这个阶段来说,由于不怎么关注,所以会难住一部分同学。
分析
存储容器的设计
在任何语言中,们希望在内存中临时存放一些数据,可以用一些官方封装好的集合(如图),比如:List、HashMap、Set等等。作为数据存储的容器。
容器的大小
当们创建一个集合对象的时候,实际上就是在内存中一次性申请一块内存空间。而这个内存空间大小是在创建集合对象的时候指定的。
比如List的默认大小是10、HashMap的默认大小是16。
长度不够怎么办
在实际开发中,们需要存储的数据量往往大于存储容器的大小。
针对这种情况,通常的做法就是扩容。
当集合的存储容量达到某个阈值的时候,集合就会进行动态扩容,从而更好的满足更多数据的存储。
(如图)List和HashMap,本质上都是一个数组结构,所以基本上只需要新建一个更长的数组,然后把原来数组中的数据拷贝到新数组就行了。
以HashMap为例,它是什么时候触发扩容以及扩容的原理是什么呢?
HashMap是如何扩容的
当HashMap中元素个数超过临界值时会自动触发扩容,这个临界值有一个计算公式。threashold=loadFactor*capacity
loadFactor的默认值是0.75,capacity的默认值是16,也就是元素个数达到12的时候触发扩容。
扩容后的大小是原来的2倍
由于动态扩容机制的存在,所以们在实际应用中,需要注意在集合初始化的时候明确指定集合的大小。
避免频繁扩容带来性能上的影响
假设们要向HashMap中存储1024个元素,如果按照默认值16,随着元素的不断增加,会造成7次扩容。
而这7次扩容需要重新创建Hash表,并且进行数据迁移,对性能影响非常大。
最后,可能有些面试官会继续问,为什么扩容因子是0.75?
为什么扩容因子是0.75
扩容因子表示Hash表中元素的填充程度,扩容因子的值越大,那么触发扩容的元素个数更多,虽然空间利用率比较高,但是hash冲突的概率会增加。
扩容因子的值越小,触发扩容的元素个数就越少,也意味着hash冲突的概率减少,但是对内存空间的浪费就比较多,而且还会增加扩容的频率。
因此,扩容因子的值的设置,本质上就是在冲突的概率以及空间利用率之间的平衡。
0.75这个值的来源和统计学里面的泊松分布有关。
(如图)我们知道,HashMap里面采用链式寻址法来解决hash冲突问题,为了避免链表过长带来时间复杂度的增加,所以链表长度大于等于7的时候,就会转化为红黑树,提升检索效率。
当扩容因子在0.75的时候,链表长度达到8的可能性几乎为0,也就是比较好的达到了空间成本和时间成本的平衡。
回答
当HashMap元素个数达到扩容阈值,默认是12的时候,会触发扩容。
默认扩容的大小是原来数组长度的2倍,HashMap的最大容量是Integer.MAX_VALUE,也就是2的31次方-1。