创意电子

标题: JDK发展记2:ArrayList常用方法源码探索(上) [打印本页]

作者: 繁茂FM    时间: 2021-10-15 16:40
标题: JDK发展记2:ArrayList常用方法源码探索(上)

                               
登录/注册后可看大图


                               
登录/注册后可看大图

上一节信赖你不但学会ArrayList构造函数的知识,更学会了先脉络后细节、连蒙带猜的思想。在之后的几节中,你将学会ArrayList常用方法的源码原理。学完之后,你将不会再被ArrayList的面试题问倒了,更可以得心应手的利用ArrayList。
今天这一节,你紧张可以学到以下几点:
让我们开始吧!
ArrayList第一次调用add方法发生了什么?


                               
登录/注册后可看大图

首先你要修改下上一节之前的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 the  end of this list.     *     * @param e element to be appended to  this list     * @return true (as  specified by {@link Collection#add})     */    public boolean add(E e) {        ensureCapacityInternal(size +  1);  // Increments modCount!!        elementData[size++] = e;        return true;    }看之前,我又要多说两句,源码的注释和方法名,有时候能资助你了解这个方法大致的作用。这是很关键的一点。好比注释的意思是add方法是追加一个具体的元素到list的末尾;方法名ensureCapacityInternal,是确保内部容量的意思。看过之后,你应该可以猜到这方法大致是确认容量相关的,可能是判断容量能否添加元素用的。
还要注意的是,你看一个方法的时候,不要着急直接从第一个行就开始看,也是先根据注释和方法名有一个大概的认识后,你需要看下整个方法的结构之后再开始。好比调用了哪些其他方法,有几个for循环或if结构。就像这个add方法,他紧张有2步,第一步是调用了一个方法,应该是确保内部容量是否够添加元素,第二步是一个数组根本赋值操作并且size++一下。
如下图所示:

                               
登录/注册后可看大图

ArrayList的数组大小为0也能可以添加元素?


                               
登录/注册后可看大图

接着当你知道了整个方法的脉络,你再来看下细节,先看第一步:ensureCapacityInternal()。
这个方法入参是size + 1,size之前说你应该看到过,就是一个成员变量int size。它的默认值是0,所以size+1后,这个方法的实参就是1,那么形参minCapacity的值就是1。(实参就是传入方法实际的值,形参就是方法括号中利用的参数名)。可以看到ensureCapacityInternal的代码清单如下:
private void  ensureCapacityInternal(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 int  calculateCapacity(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。
到这里你可以在完善下之前画的图,就会酿成如下所示:

                               
登录/注册后可看大图

也就是说calculateCapacity返回后,回到ensureCapacityInternal这个方法,会传入ensureExplicitCapacity的实参是10,因为calculateCapacity(elementData, minCapacity)= 10。
private void  ensureCapacityInternal(int minCapacity) {    ensureExplicitCapacity(calculateCapacity(elementData,  minCapacity));}接着你又会进入ensureExplicitCapacity方法,看到如下代码:
private void ensureExplicitCapacity(int  minCapacity) {        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机制的实现,下一节我会讲到的)
现在实行代码如下图所示:

                               
登录/注册后可看大图

ArrayList第一次添加元素会扩容么?


                               
登录/注册后可看大图

你接着往下看,很明显,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 to  size, 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,如下所示:

                               
登录/注册后可看大图

那么int newCapacity = oldCapacity +(oldCapacity >> 1); 实在这句话的意思就是在原有大小底子上增加一半大小。
但是由于oldCapacity=0,所以增加一半大小,newCapacity=**0+0>>1=0+0=0**。

而此时minCapacity=10。这样就会进入第一个if条件,因为newCapacity - minCapacity < 0,<strong>即0-10




欢迎光临 创意电子 (https://wxcydz.cc/) Powered by Discuz! X3.4