当前位置: 首页 > 后端技术 > Java

奇怪,为什么ArrayList的初始化容量是10?

时间:2023-04-01 19:24:27 Java

背??景看ArrayList源码的时候无意中看到ArrayList的初始容量是10,奇怪!我们都知道ArrayList和HashMap底层都是基于数组的,但是为什么ArrayList不像HashMap那样使用16作为初始容量,而是使用10呢?于是各方查找资料,验证了这个问题,本文就为大家讲述一下。为什么HashMap的初始容量是16?说到ArrayList的初始化能力,首先要回顾一下HashMap的初始化能力。以Java8源码为例,HashMap中有两个相关因子:初始容量和加载因子:/***默认初始容量-必须是2的幂。*/staticfinalintDEFAULT_INITIAL_CAPACITY=1<<4;//aka16/***构造函数中未指定时使用的加载因子。*/staticfinalfloatDEFAULT_LOAD_FACTOR=0.75f;在HashMap中,数组默认初始化容量为16,当数据填充到默认容量的0.75时,容量会翻倍。当然用户也可以在初始化的时候传入指定的大小。但是需要注意的是,最好是2的n次方的值,如果不设置为2的n次方,HashMap也会进行转换,只是多了一步操作。关于HashMap的实现原理的内容,这里不再赘述。网上已经有太多关于这个的文章了。我们需要知道的一件事是HashMap计算Key值坐标的算法,即通过对Key值进行哈希运算,然后映射到数组中的坐标。这时候HashMap的容量保证是2的n次方,那么hash运算的时候可以直接用位运算直接对内存进行运算,不用转为十进制,效率会更高。一般可以认为,HashMap之所以使用2的n次方,默认值为16,有以下考虑:减少hash碰撞;提高地图查询效率;分配太小,无法防止频繁扩展;太大的分配浪费资源;总之,HashMap之所以使用16作为默认值是为了减少哈希冲突,提高效率。ArrayList的初始容量是10吗?接下来我们先确认ArrayList的初始化容量是否为10,再讨论为什么是这个值。我们来看看Java8中ArrayList初始化容量的源码:/***Defaultinitialcapacity。*/privatestaticfinalintDEFAULT_CAPACITY=10;显然,默认的容器初始化值是10。而从JDK1.2到JDK1.6,这个值一直是10。从JDK1.7开始,在初始化ArrayList时,默认值被初始化为一个空数组:/***用于默认大小的空实例的共享空数组实例。我们*将此与EMPTY_ELEMENTDATA区分开来,以了解在添加第一个元素时要膨胀多少。*/privatestaticfinalObject[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};/***构造一个初始容量为10的空列表。*/publicArrayList(){this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}这里肯定有朋友说,Java8中ArrayList的默认初始化大小是0,不是10。而且你还会发现构造方法的注释有点奇怪:构造一个初始容量为10的空列表.我勒个去?明明是空的!存疑先看ArrayList的add方法:publicbooleanadd(Ee){ensureCapacityInternal(size+1);//递增modCount!!元素数据[大小++]=e;返回真;}在add方法中调用了ensureCapacityInternal方法,一开始进入这个方法是一个空容器,所以传入的minCapacity=1size=0:privatevoidensureCapacityInternal(intminCapacity){ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));}在上面的方法中,首先calculateCapacityCapacity:privatestaticintcalculateCapacity(Object[]elementData,intminCapacity){if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){returnMath.max(DEFAULT_CAPACITY,minCapacity);}返回最小容量;10)、传入ensureExplicitCapacity(minCapacity);thisminCapacity=10,下面是方法体:privatevoidensureExplicitCapacity(intminCapacity){modCount++;//溢出意识代码if(minCapacity-elementData.length>0)g行(最小容量);}privatevoidgrow(intminCapacity){//溢出意识代码intoldCapacity=elementData.length;intnewCapacity=oldCapacity+(oldCapacity>>1);如果(newCapacity-minCapacity<0)newCapacity=minCapacity;如果(newCapacity-MAX_ARRAY_SIZE>0)newCapacity=hugeCapacity(minCapacity);//minCapacity通常接近大小,所以这是一个胜利:elementData=Arrays.copyOf(elementData,newCapacity);}上面代码中的grow方法是用来处理扩容的,将容量扩容到原来容量的1.5倍。理解了上面的处理流程,我们会发现ArrayList的初始容量本质上还是10,只是使用了懒加载。这是Java8为节省内存而进行的优化。就这样。所以从头到尾,ArrayList的初始化容量都是10。这里再多说说懒加载的好处。当程序中有数千个ArrayList时,默认大小为10个对象意味着在创建时为底层数组分配了10个指针(40或80字节)并用空填充它们,一个空数组(填充空值)需要很多内存。如果能对数组进行惰性初始化,就可以节省大量的内存空间。Java8的变化就是为了上述目的。为什么ArrayList的初始容量是10?最后再讨论一下为什么ArrayList的初始化容量是10,其实也可以说没有原因,但是10的“感觉”还是蛮不错的,不太大,也不太小,刚刚好,养眼-抓住!首先,在讨论HashMap的时候,我们说过HashMap之所以选择2的n次方,更多的是考虑到hash算法的性能和碰撞。ArrayList不存在这个问题。ArrayList只是一个简单的增长数组,不考虑算法层面的优化。只要超过一定的值,就可以增加。因此,理论上ArrayList的容量可以是任意正值。ArrayList的文档没有解释为什么选择10,但可能是出于性能损失和空间损失之间最佳匹配的考虑。10.不要太大,不要太小,不要浪费太多的内存空间,也不要牺牲太多的性能。如果非要问当初为什么选择10,那你可能只能问这段代码的作者“JoshBloch”了。如果你仔细看,你会发现其他一些有趣的初始化容量数字:ArrayList-10Vector-10HashSet-16HashMap-16HashTable-11ArrayList和Vector的初始容量一样,都是10;HashSet和HashMap的初始容量相同,都是16;而HashTable单独使用11是另一个有趣的问题。总结有很多问题没有明确的原因和明确的答案。就像一个女孩对你没有感觉,也许是因为你不够好,或者她已经爱上了别人,但很有可能你不知道答案。但是在寻找原因和答案的过程中,还是可以学到很多,成长很多。没有比较就没有伤害,比如HashMap和ArrayList的比较,没有比较就不知道适不适合,比如HashMap和ArrayList。当然你也可以试试特立独行的HashTable,可能适合你。博主简介:《SpringBoot技术内幕》技术书籍作者,热爱研究技术,写干货技术文章。公众号:《程序新视野》,博主的公众号,欢迎关注~技术交流:请联系博主微信号:zhuan2quan