问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

深度解析ArrayList的扩容机制

发布网友 发布时间:2024-09-29 15:26

我来回答

1个回答

热心网友 时间:2024-11-01 13:06

在Java中,ArrayList是一个使用非常频繁的集合类型,它的底层是Object数组,所以它拥有数组所拥有的特性,比如支持随机访问,所以查询效率高,但插入数据需要移动元素,所以效率低。

先来看看若是调用ArrayList的无参构造方法,会发生什么?

transientObject[]elementData;privatestaticfinalObject[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};publicArrayList(){this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}

在构造方法中,它将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData,这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的Object数组,而elementData就是ArrayList实际存储数据的容器。

由此可知,ArrayList在调用无参构造方法时创建的是一个长度为0的空数组,当调用add()方法添加元素时,ArrayList才会触发扩容机制:

publicbooleanadd(Ee){ensureCapacityInternal(size+1);//IncrementsmodCount!!elementData[size++]=e;returntrue;}

add()方法的第一行即是执行扩容流程:

privatevoidensureCapacityInternal(intminCapacity){ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));}

该方法又会先计算扩容容量:

privatestaticintcalculateCapacity(Object[]elementData,intminCapacity){if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){returnMath.max(DEFAULT_CAPACITY,minCapacity);}returnminCapacity;}

初始elementData就是一个空数组,条件成立,它会从DEFAULT_CAPACITY和minCapacity中选择一个最大值返回,其中DEFAULT_CAPACITY表示默认的初始容量,它的值为10:

privatestaticfinalintDEFAULT_CAPACITY=10;

而minCapacity是add()方法传递过来的,值为size+1:

ensureCapacityInternal(size+1);//IncrementsmodCount!!

所以calculateCapacity()方法将返回10,之后调用ensureExplicitCapacity()方法:

privatevoidensureExplicitCapacity(intminCapacity){modCount++;//overflow-consciouscodeif(minCapacity-elementData.length>0)grow(minCapacity);}

首先让modCount++,这是用来记录数组被修改次数的变量,我们先不管它,此时minCapacity的值为10,elementData.length的值为0,条件成立,执行grow()方法:

privatevoidgrow(intminCapacity){//overflow-consciouscodeintoldCapacity=elementData.length;intnewCapacity=oldCapacity+(oldCapacity>>1);if(newCapacity-minCapacity<0)newCapacity=minCapacity;if(newCapacity-MAX_ARRAY_SIZE>0)newCapacity=hugeCapacity(minCapacity);//minCapacityisusuallyclosetosize,sothisisawin:elementData=Arrays.copyOf(elementData,newCapacity);}

注意这一行代码:

intnewCapacity=oldCapacity+(oldCapacity>>1);

先将旧容量右移1位,再加上旧容量就得到了新容量,正数右移1位相当于除以2,在该基础上加旧容量,则等价于新容量=旧容量*1.5,所以才有ArrayList每次扩容为旧容量的1.5倍的说法,最后调用Arrays.copyOf()方法进行拷贝,并将elementData指向新数组,而旧数组因为没有引用指向它,很快就会被垃圾收集器回收掉。

当第二次调用add()方法时,程序依然要走到扩容方法:

privatestaticintcalculateCapacity(Object[]elementData,intminCapacity){if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){returnMath.max(DEFAULT_CAPACITY,minCapacity);}returnminCapacity;}

但此时的elementData已经不是空数组了,所以直接返回当前size+1,即:2,接着调用:

privatevoidensureExplicitCapacity(intminCapacity){modCount++;//overflow-consciouscodeif(minCapacity-elementData.length>0)grow(minCapacity);}

因为此时minCapacity小于数组长度,所以if判断不会成立,也就不会发生扩容。

当添加第11个元素时,ArrayList应该会触发第二次扩容,来看源代码:

privatestaticintcalculateCapacity(Object[]elementData,intminCapacity){if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){returnMath.max(DEFAULT_CAPACITY,minCapacity);}returnminCapacity;}

minCapacity的值为11,紧接着调用它:

privatevoidensureExplicitCapacity(intminCapacity){modCount++;//overflow-consciouscodeif(minCapacity-elementData.length>0)grow(minCapacity);}

此时minCapacity的值大于elementData的长度,条件成立,触发扩容机制:

privatevoidgrow(intminCapacity){//overflow-consciouscodeintoldCapacity=elementData.length;intnewCapacity=oldCapacity+(oldCapacity>>1);if(newCapacity-minCapacity<0)newCapacity=minCapacity;if(newCapacity-MAX_ARRAY_SIZE>0)newCapacity=hugeCapacity(minCapacity);//minCapacityisusuallyclosetosize,sothisisawin:elementData=Arrays.copyOf(elementData,newCapacity);}

将原容量10右移一位得到5,再加上原容量10得到15,所以新数组的容量为15,最后对数组进行拷贝扩容就完成了。

当ArrayList进行第三次扩容后容量会是多少呢?我们知道,新容量一定是旧容量的1.5倍,而15*1.5=22.5,那么新容量到底是22还是23呢?所以,如果你只知道新容量是旧容量的1.5倍,这个问题你就无法知道。事实上,ArrayList底层是通过移位操作计算得到的新容量。所以新容量应该等于15>>1+15=22,由此可得,ArrayList经过第三次扩容后容量为22。

然而在addAll()方法中,扩容机制会有一定的变化,比如:

publicbooleanadd(Ee){ensureCapacityInternal(size+1);//IncrementsmodCount!!elementData[size++]=e;returntrue;}4

执行完addAll()方法后,ArrayList的容量应该是多少呢?是15吗?来看看源代码:

publicbooleanadd(Ee){ensureCapacityInternal(size+1);//IncrementsmodCount!!elementData[size++]=e;returntrue;}5

它将调用ensureCapacityInternal()方法进行扩容,并传入size+numNew=11:

privatestaticintcalculateCapacity(Object[]elementData,intminCapacity){if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){returnMath.max(DEFAULT_CAPACITY,minCapacity);}returnminCapacity;}

因为初始elementData是一个空数组,符合条件,所以它将返回DEFAULT_CAPACITY和minCapacity中较大的那个,结果是minCapacity较大,所以返回11,这就导致addAll()方法执行结果后ArrayList的容量为11。

addAll()方法总是选择扩容一次后的容量与旧容量加上添加的元素个数的容量中取一个最大值作为新的容量,比如:当前ArrayList中有10个元素,而addAll()方法需要添加6个元素,当ArrayList触发扩容后的新容量应该为15,而旧容量加上需要添加的元素容量为16,从中取一个较大值为16,所以新容量应该为16。

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
如何画出3d立体画 我想问问收到发票怎么做分录 学生平板电脑改成正常平板教程 别人拿qq骂我,但发的是语音,我举报后他却没有什么反应,还是和原来一... 信报箱可以放牛奶吗 2024贵州中高考生免费景点有哪些 这图片出自哪个游戏或同人动漫? 中华人民共和国可再生能源法修正案说明 可再生资源的利用措施 中广核的校招待遇好不好? 瓷砖店面装修材料有哪些? 我有好多蛀牙!!疼了一个礼拜,一直输液消炎..现在好多了,去补牙吗? ...只是牙齿表面有些黑点,没有洞洞~,很害怕会形成牙洞~想钻牙!_百度... 写轮眼的来历 火影中的那个轮回眼.到底是干嘛的.!/ 真正的宇智波斑为什么会有轮回眼,本体斑出来时为什么说,长门这小鬼,成 ... 合肥旭辉天阜越江来售楼中心电话是多少? 合肥旭辉望江来地址在哪里? 理科女生242分适合学什么专业? 橱柜定制哪个品牌好性价比高 做橱柜用什么材料好 请问有什么好点的厨柜牌子么? 请问整体厨房哪个牌子好,有谁比较了解可以推荐一下吗? 什么牌子的橱柜比较好 整体橱柜什么牌子好?推荐几款性价比高的整体橱柜 很羽泉歌手介绍 羽泉 自己的介绍 怎样保存视频到相册里 怎样把相册的视频保存到相册? 如何把槐花制作成美食? 槐花可以做成什么家常美食? ArrayList&lt;NameValuePair&gt;();这个Java代码什么意思,尤其是array List&lt;Integer&gt;list =new ArrayList&lt;Integer&gt;(); &lt;Integer&gt; 什么... 请问215/60R17'96H的轮胎多少钱一个?谢谢! 刚满一年,胎被马路牙子割破了,选胎求指教? 齐鲁银行可以用卡号密码在取款机上取钱吗 齐鲁银行定期卡和折的区别 淘宝直播PC客户端常见使用问题及解决方案 充满正能量的阳光句子(特别励志的文案) 为人处世的正能量励志的句子 早安高质量的正能量的朋友圈语录 励志早安正能量发朋友圈 130毫升的擦脸油用了一半了能带上飞机吗 关于坚强励志的句子发朋友圈(句句阳光正能量,句句戳心) 早上说说短句子 满满的正能量 2024年员工试用期转正工作总结范文 2024年个人试用期工作总结范文 打印机上显示的fingerprint单词什么意思 河南永安特种工程有限公司怎么样? 武汉羿德永安建设工程有限公司怎么样? 江苏永达交通设施有限公司怎么样? 用ps如何把照片中的文字抠出来?