说这个之前,你先得搞清楚这个 minCapacity 是啥,它现在其实就是底层数组将要添加的第几个元素,看看上一步

 ensureCapacityInternal(size + 1);

这里 size+1 了,所以现在 minCapacity 相当于是 1,也就是说将要向底层数组添加第一个元素,这一点的理解很重要,所以从 minCapacity 的字面意思理解也就是“最小容量”,我现在将要添加第一个元素,那你至少给我保证底层数组有一个空位置,不然怎么放数据嘞。

重点来了,因为第一次添加,底层数组没有一个位置,所以需要先确定下来一共有多少个位置,就是献给数组一个默认的长度

于是这里给重新赋值了(只有第一次添加数据才会执行这步,这一步就是为了指定默认数组长度的,指定一次就 ok 了)

minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

这怎么赋值的应该知道嘛,哪个大取哪个,那我们要看看 DEFAULT_CAPACITY 是多少了

 /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

ok,明白了,这就是 ArrayList 的底层数组 elementData 初始化容量啊,是 10,记住了哦,那么现在 minCapacity 就是 10 了,我们再接着看下面的代码,也即是:

ensureExplicitCapacity(minCapacity);

进去看看吧:

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

也比较简单,现在底层数组长度肯定还不到 10 啊,所以我们继续看 grow 方法

  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);
    }

咋一看,判断不少啊,干啥的都是,突然看到了 Arrays.copyOf,知道这是啥吧,上面可是特意讲过的,原来这是要进行数组拷贝啊,那这个 elementData 就是原来的数组,newCapacity 就是新数组的容量

我们一步步来看代码,首先是

 int oldCapacity = elementData.length;

得到原来数组的容量,接着下一步:

int newCapacity = oldCapacity + (oldCapacity >> 1);

这是得到新容量的啊,不过后面的这个 oldCapacity >> 1 有点看不懂啊,其实这 oldCapacity >> 1 就相当于 oldCapacity /2,这是移位运算,感兴趣的自行搜索学习。

知道了,也就是扩容为原来的 1.5 倍,接下来这一步:

if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

因为目前数组长度为 0,所以这个新的容量也是 0,而 minCapacity 则是 10,所以会执行方法体内的赋值操作,也就是现在的新容量成了 10。

接着这句代码就知道怎么回事了

elementData = Arrays.copyOf(elementData, newCapacity);

不知道你发现没,这里饶了一大圈,就是为了创建一个默认长度为 10 的底层数组。

底层数组长度要看 ensureCapacityInternal

ensureCapacityInternal 这个方法就像个守卫,时刻监视着数组容量,然后过来一个数值,也就是说要向数组添加第几个数据,那 ensureCapacityInternal 需要思考思考了,思考啥呢?当然是看底层数组有没有这么大容量啊,比如你要添加第 11 个元素了,那底层数组长度最少也得是 11 啊,不然添加不了啊,看它是怎么把关的

 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

记住了这段代码

 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

它的存在就是为了一开始创建默认长度为 10 的数组的,当添加了一个数据之后就不会再执行这个方法,所以重难点是这个方法:

ensureExplicitCapacity(minCapacity);

也就是真正的把关在这里,看它的实现:

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

怎么样,看明白了吧,比如你要添加第 11 个元素,可是我的底层数组长度只有 10,不够啊,然后执行 grow 方法,干嘛执行这个方法,它其实就是用来扩容的,不信你再看看它的实现,上面已经分析过了,这里就不说了。

假如你要添加第二个元素,这里底层数组长度为 10,就不需要执行 grow 方法,因为根本不需要扩容啊,所以这一步实际啥也没做(有个计数操作):然后就直接在相应位置赋值了。

小结

所以这里很重要的一点就是理解这一步传入的值的意义:

ensureCapacityInternal(size + 1);

简单点就是要向底层数组中添加第几个元素了,然后开始进行一系列的判断,容量够的话直接返回,直接赋值,不够的话就执行 grow 方法开始扩容。

主要判断就在这里:

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

具体的扩容是这里

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);
    }

这里需要注意这段代码

if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

这段代码只有在第一次添加数据的时候才会执行,也是为创建默认长度为 10 的数组做准备的,因为这个时候原本数组长度为 0,扩容后也是 0,而 minCapacity 为默认值 10,所以会执行这段代码。

但是一旦添加数据之后,底层数组默认就是 10 了,再加上之前的判断,这里的 newCapacity 一定会比 minCapacity 大,这个点需要了解。

看看 ArrayList 的有参构造函数

我们上面着重分析了下 ArrayList 的无参构造函数,下面再来看看它的有参构造函数:

ArrayList arrayList1 = new ArrayList(100);

看看这个构造函数张啥样?

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

我去,这不就是直接创建嘛,然后还有这个:

else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        }

我们看看这个 EMPTY_ELEMENTDATA

private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

ok,现在你可以回答我们开篇提的那个问题了吧。

我们以上对 ArrayList 的源码有了一定的认识之后,我们再来看看 ArrayList 的读取,替换和删除操作时怎样的?

ArrayList 的其他操作

经过上面的分析,我相信你对 ArrayList 的其他诸如读取删除等操作也没啥问题,一起来看下。

读取操作

看源码

  public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

代码很简单,rangeCheck 就是用来判断数组是否越界的,然后直接返回下标对应的值。

删除操作

看源码

   public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

代码相对来说多一些,要理解这个,可以仔细看看我上面对“关于数组删除的一些思考”的分析,这里是一样的道理。

替换操作

看源码

   public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

其实就是把原来的值覆盖,没啥问题吧 😄

和 vector 很像

这个想必大家都知道,ArrayList 和 vector 是很像的,前者是线程不安全,后者是线程安全,我们看一下 vector 一段源码就明白了

  public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

没错,区别就是这么明显!

总结

到这里,我们基本上把 ArrayList 的相关重点都过了一遍,对于 ArrayList 来说,重点就是分析它的无参构造函数的执行,经过分析,我们知道了它有个数组拷贝的操作,这块是会影响到它的一些操作的时间复杂度的,关于这点,就留给大家取思考吧!

好了,今天就到这里,大家如果有什么问题,欢迎留言,一起交流学习!