HashMap 与 ArrayList同样的数据,哪个会先内存溢出,你知道吗?

HashMap存放数据过多的话,会出现内存溢出,代码如下:

这个例子,我经过多次运算,发现在40万的数据并不会内存溢出,如果要复现此问题,需要修改idea的内存配置,才会复现。但现在的机器的内存逐渐的增大,硬件配置的提高,应该可以容纳更多的数据。本人机器是windows64,内存12G配置,idea的配置为 -Xms286M -Xmx1024M,在单独运行此程序时,数据量加到千万级别才会复现出此问题。但在生产环境中,如果放的是复杂对象,可能同样配置的机器存放的数据量会小一些。

但如果换成list存放,则同样的配置存放的数据比HashMap要多一些,本人就针对此现象进行分析一下几点:

  • HashMap和ArrayList的长度都是动态增加的,不过两者的扩容机制不同,而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。。也就是说HashMap比ArrayList多了一次封装,多出了一倍的对象。其中HashMap的扩容机制代码如下(resize(2 *table.length)这就是扩容核心代码):

HashMap put 方法的部分代码

插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

所以hashMap的size大于数组的0.75倍时,就开始扩容,经过计算得知(怎么计算的,以文中例子来说,查找2的N次幂大于40万的最小值即为数组的最大长度,再乘以0.75,也就是最后一次扩容点,计算的结果是N=19),在Map的size为393216时,符合了扩容条件,于是393216个元素开始搬家,要扩容则需要申请一个长度为1048576(当前长度的两倍,2的20次方)的数组,如果此时内存不足以支撑此运算,就会爆出内存溢出。这个就是这个问题的根本原因。

  • 我们思考一下ArrayList的扩容策略,它是在小于数组长度的时候才会扩容1.5倍,经过计算得知,ArrayLsit在超过80万后(一次加两个元素,40万的两倍),最近的一次扩容是在size为1005305时同样的道理,如果此时内存不足以申请扩容1.5倍时的数组,也会出现内存溢出。

综合来说,HashMap比ArrayList多了一层Entry的底层封装对象,多占用了内存,并且它的扩容策略是2倍长度的递增,同时还会根据阈值判断规则进行判断,因此相对于ArrayList来说,同样的数据,它就会优先内存溢出。

也许大家会想到,可以在声明时指定HashMap的默认长度和加载因子来减少此问题的发生,可以缓解此问题,可以不再频繁的进行数组扩容,但仍避免不了内存溢出问题,因为键值对的封装对象Entry还是少不了的,内存依然增长比较快,所以尽量让HashMap中的元素少量并简单一点。也可以根据需求以及系统的配置来计算出,自己放入map中的数据会不会造成内存溢出呢?


你可能感兴趣的:(HashMap 与 ArrayList同样的数据,哪个会先内存溢出,你知道吗?)