JDK发展记2:ArrayList常用方法源码探索(上)
https://p5.toutiaoimg.com/large/pgc-image/c76bf7a66c2c4e4ba468844c93a46287https://p9.toutiaoimg.com/large/pgc-image/db324920153c4a1ebbff6d22bea6e02e
上一节信赖你不但学会ArrayList构造函数的知识,更学会了先脉络后细节、连蒙带猜的思想。在之后的几节中,你将学会ArrayList常用方法的源码原理。学完之后,你将不会再被ArrayList的面试题问倒了,更可以得心应手的利用ArrayList。
今天这一节,你紧张可以学到以下几点:
[*]ArrayList扩容原理
[*]影响ArrayList性能的根本原因是什么
[*]学会新的源码阅读思想和方法
让我们开始吧!
ArrayList第一次调用add方法发生了什么?
https://p5.toutiaoimg.com/large/pgc-image/fa273afa2a814fc480f69f999b437c75
首先你要修改下上一节之前的Demo,修改如下:
import java.util.ArrayList;import java.util.List;public class ArrayListDemo { public static void main(String[] args) { //默认大小是0 List hostList = new ArrayList(); hostList.add("host1"); }}上面代码,假设你通过add方法向hostList添加了1个host主机地址。ArrayList第一次调用add方法发生了什么?
你可以点击进去,看到如下代码清单:
/** * Appends the specified element to theend of this list. * * @param e element to be appended tothis list * @return true (asspecified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size +1);// Increments modCount!! elementData = e; return true; }看之前,我又要多说两句,源码的注释和方法名,有时候能资助你了解这个方法大致的作用。这是很关键的一点。好比注释的意思是add方法是追加一个具体的元素到list的末尾;方法名ensureCapacityInternal,是确保内部容量的意思。看过之后,你应该可以猜到这方法大致是确认容量相关的,可能是判断容量能否添加元素用的。
还要注意的是,你看一个方法的时候,不要着急直接从第一个行就开始看,也是先根据注释和方法名有一个大概的认识后,你需要看下整个方法的结构之后再开始。好比调用了哪些其他方法,有几个for循环或if结构。就像这个add方法,他紧张有2步,第一步是调用了一个方法,应该是确保内部容量是否够添加元素,第二步是一个数组根本赋值操作并且size++一下。
如下图所示:
https://p5.toutiaoimg.com/large/pgc-image/1435af956bae42ed84356ffad943b163
ArrayList的数组大小为0也能可以添加元素?
https://p6.toutiaoimg.com/large/pgc-image/c941cf88cc0b4a398b6749abc9231828
接着当你知道了整个方法的脉络,你再来看下细节,先看第一步:ensureCapacityInternal()。
这个方法入参是size + 1,size之前说你应该看到过,就是一个成员变量int size。它的默认值是0,所以size+1后,这个方法的实参就是1,那么形参minCapacity的值就是1。(实参就是传入方法实际的值,形参就是方法括号中利用的参数名)。可以看到ensureCapacityInternal的代码清单如下:
private voidensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}接着你可以看到这里又调用了calculateCapacity方法和ensureExplicitCapacity方法。我们先进入calculateCapacity看下,你可以记下它的实参是minCapacity=1,elementData还记得么?是ArrayList核心的那个成员变量Object[]数组。calculateCapacity代码如下:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA= {};transient Object[] elementData; private static final int DEFAULT_CAPACITY = 10;private static intcalculateCapacity(Object[] elementData, int minCapacity) { if (elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; }这里你可以看熟悉的两个成员变量:
elementData和DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
在ArrayList无参构造函数的时候就看到过。第一次调用add方法的时候,它们俩的引用地址和值都是应该一样的,因为在构造函数中有过这么一句话this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
所以这里进入第一个if条件,然后利用DEFAULT_CAPACITY和minCapacity作比力,可以看到DEFAULT_CAPACITY这个变量的值是10,也就是说这里Math.max操作后会返回10。
到这里你可以在完善下之前画的图,就会酿成如下所示:
https://p5.toutiaoimg.com/large/pgc-image/a00af4f1de1d4b25bc1c533d34484e49
也就是说calculateCapacity返回后,回到ensureCapacityInternal这个方法,会传入ensureExplicitCapacity的实参是10,因为calculateCapacity(elementData, minCapacity)= 10。
private voidensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));}接着你又会进入ensureExplicitCapacity方法,看到如下代码:
private void ensureExplicitCapacity(intminCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length> 0) grow(minCapacity); }此时形参minCapacity也就是刚才传入的10,你可以从名字上看这个方法,ensureExplicitCapacity意思是确保准确的容量的意思。还是先看下方法的脉络,第一行有一个modCount++,之后你可以看到一行代码注释,overflow-consciouscode意思是说具有溢出意思的代码,之后有一个if条件,满足会进入grow方法。
到这里你可以连蒙带猜下,这里的grow方法,是不是就是说容量不够,会举行增长,也就是扩容呢?
因为我们知道,无参构造函数创建的ArrayList大小默认是一个为0的Object[]数组elementdata。而且elementdata.length肯定是0,难道也能添加元素么?肯定是不可以的。
接着我们逐行看下,第一行的modCount似乎不太知道是什么,你可以点击下它,看到它是一个成员变量,而且有一大堆注释,似乎也没看懂什么意思。所以这里要告诉大家另一个看源码的思想了:抓大放小。好比这里看上去modeCount和添加操作没啥关系,就先跳过!(实在这个modCount是并发操作ArrayList时,fail-fast机制的实现,下一节我会讲到的)
现在实行代码如下图所示:
https://p26.toutiaoimg.com/large/pgc-image/ed4c8382516a48f688360f9416a12d74
ArrayList第一次添加元素会扩容么?
https://p6.toutiaoimg.com/large/pgc-image/97c957dd6bbe4478b3e4ffcd0a8140cb
你接着往下看,很明显,minCapacity=10,elementData这个空数组length肯定是0。所以10-0>0,会满足if条件,进入grow方法,它的实参是10。它的代码清单如下:
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity= elementData.length; int newCapacity= oldCapacity + (oldCapacity >> 1); if (newCapacity- minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE >0) newCapacity =hugeCapacity(minCapacity); // minCapacity is usually close tosize, so this is a win: elementData = Arrays.copyOf(elementData,newCapacity); }grow方法形参minCapacity的值是10,你进入这个grow方法后,从脉络上来看,有两个if分支,有两个局部变量oldCapacity和newCapacity,另有一步elementData通过Arrays.copyOf方法的赋值操作。
而且oldCapacity应该是0,因为我们知道elementData数组是空的。接着你会看到一句:intnewCapacity = oldCapacity +(oldCapacity >> 1);
这句话是什么意思呢?实在就是oldCapacity右位移1位。如果你还记得盘算机底子中的话,右移一位,有点雷同于除以2,底层是二进制的运算而已。这里可以举个例子:
好比oldCapacity=10,如果先左移1,就是乘以2,在右移 1就是除以2,如下所示:
https://p5.toutiaoimg.com/large/pgc-image/72721398576b43faa98bca6b3e95d52b
那么int newCapacity = oldCapacity +(oldCapacity >> 1); 实在这句话的意思就是在原有大小底子上增加一半大小。
但是由于oldCapacity=0,所以增加一半大小,newCapacity=**0+0>>1=0+0=0**。
而此时minCapacity=10。这样就会进入第一个if条件,因为newCapacity - minCapacity < 0,<strong>即0-10
页:
[1]