认识ArrayList + 源码解析

ArrayList的特性

<1>. 底层基于数组实现
<2>. 容器内元素有序可重复,可放null
<3>. 线程不安全
大概这几点,后续想到再追加。

如何认识ArrayList

本文章将自己按照源码一步一步实现自定义的ArrayList,并加以相应的中文描述和见解。

按照源码创建自定义的ArrayList

DIY ArrayList“全”代码

题目的“全”是代表目前写到的所有代码。

重复一次,代码是按Java源码写的(除FIXME标签的代码)。解析这个相当于解析源码,一步一步解析更容易理清源码,不被其它代码打乱。

代码有两个警告:什么 is not used这种,不必理会。

下面的代码目前只是做了一个简单的ArrayList容器,只有两个功能一个是往容器加元素,另一个是从容器获取元素。

FIXME标签是用来输出信息的,方便我们了解程序运行过程。

package spring.boot.study.javasourcecode;

import java.util.Arrays;

public class ArrayList_DIY<E> {

    transient Object[] elementData;

    transient int modCount = 0;

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    private int size;

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

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

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList_DIY(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);
        }
    }

    public ArrayList_DIY() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    public void rangeCheck(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }

    @SuppressWarnings("unchecked")
    private E elementData(int index) {
        return (E) elementData[index];
    }

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

    public int size() {
        return size;
    }

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

    }

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0) {

            // FIXME
            if (ArrayListTest.isTrace()) {
                System.out.println("需要进行扩容操作!");
            }

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

        // FIXME
        if (ArrayListTest.isTrace()) {
            System.out.println("旧容量=" + oldCapacity);
            System.out.println("新容量=" + newCapacity);
        }

        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

}

DIY ArrayList源码解析

public class ArrayList_DIY<E> {

类名是ArrayList_DIY,类名后的表示泛型类,在实例化对象时需要指定泛型类型,比如:

ArrayList_DIY<Integer> arrayList1_DIY = new ArrayList_DIY<>();

这里就指定了ArrayList_DIY容器只能放Integer。其实它是伪泛型,关于伪泛型,大家想了解更多可以自行百度。

底层实现是用数组:

transient Object[] elementData;

这个Object数组就是用来装元素的。

构造函数

/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList_DIY(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);
        }
    }

    public ArrayList_DIY() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

类中有两个构造函数,第一个是有参数构造函数,形参是int类型,通过其new对象时该形参就是容器的容量;一个是无参构造函数,通过其new对象时,容器的容量为默认容量,默认容量是10,看下面的代码。

通过上面这两个构造函数new出来的容器,一开始都是空的,即没有任何元素。

默认容量:

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

DIY ArrayList功能解析

添加元素功能(add方法)

public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

源码如上挺简单的,add方法有一个参数(即需要添加的元素),该参数的类型就是在实例化对象时指定的泛型。

elementData[size++] = e;这语句才是真正的添加元素,ensureCapacityInternal(size + 1);这语句主要作用是判断容器的容量是否够添加一个新元素,不够就扩容。通俗一点说就像我们拿箱子装东西一样,装东西很简单,放进去就可以了,但是有个前提条件就是箱子的容量可以装得下这个东西,如果装不下我们就扩大箱子

看一下ensureCapacityInternal(int minCapacity)方法。

private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

    }

先看一下ensureCapacityInternal方法的形参minCapacity,意思大概是添加这个元素需要最小的容量值,这个方法是在add方法里被调用的,add方法传了什么值给它呢,
size + 1,那么size是多少呢?看下面:

private int size;

size在通过上面介绍的两个构造函数new对象的时候,是默认值0。

所以当我们要add元素时,容器中最小的容量应当为当前容器的size 再加1(即将新增的一个元素),才能新增元素,否则要扩容再新增元素。

ensureCapacityInternal(int minCapacity)方法再去调用ensureExplicitCapacity(calculateCapacity(elementData, minCapacity))方法,
ensureExplicitCapacity方法的实参是calculateCapacity(elementData, minCapacity)方法。

所以我们先看calculateCapacity(Object[] elementData, int minCapacity)方法。

calculateCapacity方法顾名思义就是比较新增一个元素需要的最小容量(minCapacity)和容器当前容量(),孰大孰小?

calculateCapacity方法代码如下:

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,
DEFAULTCAPACITY_EMPTY_ELEMENTDATA是什么呢?看下面

/**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

大概意思是啥呢?意思大概是有着默认容量的空的容器,其实是配合无参构造函数使用的。

无参构造函数:

public ArrayList_DIY() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

通过无参构造函数new对象

ArrayList_DIY<Integer> arrayList1_DIY = new ArrayList_DIY<>();

你像上面这样new对象时,底层的elementData就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA。

回到判断elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA?

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

什么时候相等呢?很简单就是通过无参构造函数new对象且更要add第一个元素时是相等的,因为add的是第一个元素所以minCapacity必定是1。

其实只按照我们目前的代码的话,完全可以不用return Math.max(DEFAULT_CAPACITY, minCapacity);这个语句,直接返回DEFAULT_CAPACITY就OK,因为DEFAULT_CAPACITY必定大于minCapacity。但是为何比较呢?凭经验猜想因为后面还有构造函数用来new对象时就初始化很多值,这里暂时还没用到。

再来看一下ensureExplicitCapacity(int minCapacity)方法

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

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

modCount++;是用来记录modify的次数,忘记了它的作用了,看它的源码解释,太啰嗦了,可以复制到有道词典查看,这里省略。源码如下:

    /**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;

if (minCapacity - elementData.length > 0)这判断如果是true,就是说minCapacity(size[原有元素数量] + 1[新添加的1个元素])大于容器(elementData)的容量(length ),那肯定添加不成功,所以需要先扩容;如果是false,false的情况有一种是两者相减=0,这种情况就是容器可以装下且是最后一个(再来一个就得扩容)。

为true时,需要扩容。grow(minCapacity);这个语句就是扩容,参数是minCapacity。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);
    }

int oldCapacity = elementData.length;拿到当前容器的容量值,
int newCapacity = oldCapacity + (oldCapacity >> 1);新的容量为原来容量 + 原来容量右移1,关于位运算的左移和右移可以自行百度。位运算是最快的运算,因为机器记录的就是二进制码。

if (newCapacity - minCapacity < 0)得到新容量再与需要的最小容量作比较,小于0的话证明容量不够,则新容量等于需要的最小容量,即newCapacity = minCapacity;

if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

上面这语句先不管。大概就是在容量很大时做一些措施。

elementData = Arrays.copyOf(elementData, newCapacity);这个语句才是真正的获取新容量的容器。但是它的hashcode是一样的,有点想不通,感觉是和本地方法copy有关。

再看看Arrays.copyOf方法:

    /**
     * Copies the specified array, truncating or padding with nulls (if necessary)
     * so the copy has the specified length.  For all indices that are
     * valid in both the original array and the copy, the two arrays will
     * contain identical values.  For any indices that are valid in the
     * copy but not the original, the copy will contain <tt>null</tt>.
     * Such indices will exist if and only if the specified length
     * is greater than that of the original array.
     * The resulting array is of exactly the same class as the original array.
     *
     * @param <T> the class of the objects in the array
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @return a copy of the original array, truncated or padded with nulls
     *     to obtain the specified length
     * @throws NegativeArraySizeException if <tt>newLength</tt> is negative
     * @throws NullPointerException if <tt>original</tt> is null
     * @since 1.6
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

copyOf是重载函数,由少参数的重载函数调用多参数的重载函数,这个是经常用到的,真正的处理逻辑写在多参数的重载函数里。
下面就是真正的逻辑:

    /**
     * Copies the specified array, truncating or padding with nulls (if necessary)
     * so the copy has the specified length.  For all indices that are
     * valid in both the original array and the copy, the two arrays will
     * contain identical values.  For any indices that are valid in the
     * copy but not the original, the copy will contain <tt>null</tt>.
     * Such indices will exist if and only if the specified length
     * is greater than that of the original array.
     * The resulting array is of the class <tt>newType</tt>.
     *
     * @param <U> the class of the objects in the original array
     * @param <T> the class of the objects in the returned array
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @param newType the class of the copy to be returned
     * @return a copy of the original array, truncated or padded with nulls
     *     to obtain the specified length
     * @throws NegativeArraySizeException if <tt>newLength</tt> is negative
     * @throws NullPointerException if <tt>original</tt> is null
     * @throws ArrayStoreException if an element copied from
     *     <tt>original</tt> is not of a runtime type that can be stored in
     *     an array of class <tt>newType</tt>
     * @since 1.6
     */
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

注意System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));,调用本地方法arraycopy方法。Math.min(original.length, newLength)这语句就是保证不会出错,怎么说?它的作用就是比较original.length和newLength的大小返回小的,如果相等,返回的值就是相等的。如果旧容器的长度(100)大于新容器的新长度(90),那么新容器只能装90个,再往下装就溢出了,所以这个语句就是为了防止出错。一般情况,旧容器的长度小于新容器的新长度,这样才能把旧数据原封不动的放到新容器。

测试添加元素功能(测试add方法)

测试代码:

package spring.boot.study.javasourcecode;

public class ArrayListTest {

    private final static boolean trace = true;

    public static boolean isTrace() {
        return trace;
    }

    public static void main(String[] args) {

        ArrayList_DIY<Integer> arrayList1_DIY = new ArrayList_DIY<>(); // 通过无参构造函数new对象,默认的容器容量是10

        /**
         * 测试往容器里添加元素数组的功能。
         * 往容器添加11个元素,添加到第11个时,容器需要扩容。
         * 因为是无参构造函数new对象,在添加第一个元素时也会进行扩容,扩容到默认的容器容量10
         */
        for (int i = 0; i < 11; i++) {
            arrayList1_DIY.add(i);
        }

        System.out.println("arrayList1_DIY size=" + arrayList1_DIY.size());

    }
}

输出结果:

需要进行扩容操作!
旧容量=0
新容量=10
需要进行扩容操作!
旧容量=10
新容量=15
arrayList1_DIY size=11

从输出结果可以知道,容器进行了两次扩容,一次是在添加第一个元素时,另一次是在添加第11个元素时。

总结添加元素功能(总结add方法)

当往容器添加元素时,需要先判断容器当前的容量是否可以装下一个新的元素,可以就直接添加否则先扩容再添加。如果容量=0则先扩容到默认容量即10,后续扩容的新容量是当前容量值(X) + 当前容量值(X)右移1

至此,add方法解读完成了。

获取元素功能(get方法)

get方法

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

代码很简单,return elementData(index);这语句是真正获取容器里指定位置的元素,调用elementData方法获取元素,rangeCheck(index);这语句是用来干嘛呢?顾名思义就是范围检查的,检查是否超过容器的范围,事实上可有可无,Java本身机会抛出java.lang.ArrayIndexOutOfBoundsException异常,这里的作用是自定义ArrayIndexOutOfBoundsException异常信息。先看一下rangeCheck方法代码:

rangeCheck方法(数组下标是从0开始)

public void rangeCheck(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }

outOfBoundsMsg方法

private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

测试没有rangeCheck方法,Java本身也会抛ArrayIndexOutOfBoundsException异常。
Java自身ArrayIndexOutOfBoundsException异常信息

index为负数

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -19

index为整数且大于size - 1

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 19

source code自定义的ArrayIndexOutOfBoundsException异常信息,报出来的信息更合适开发人员追踪。

Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 19, Size: 11

再看看elementData方法

 @SuppressWarnings("unchecked")
    private E elementData(int index) {
        return (E) elementData[index];
    }

很简单就是从elementData数组获取指定位置为index的元素,再做类型转换,因为elementData数组是Object,转换为用户指定的泛型。

测试获取元素功能(测试get方法)

测试代码:

package spring.boot.study.javasourcecode;

public class ArrayListTest {

    private final static boolean trace = true;

    public static boolean isTrace() {
        return trace;
    }

    public static void main(String[] args) {

        ArrayList_DIY<Integer> arrayList1_DIY = new ArrayList_DIY<>(); // 通过无参构造函数new对象,默认的容器容量是10

        /**
         * 测试往容器里添加元素数组的功能。
         * 往容器添加11个元素,添加到第11个时,容器需要扩容。
         * 因为是无参构造函数new对象,在添加第一个元素时也会进行扩容,扩容到默认的容器容量10
         */
        for (int i = 0; i < 11; i++) {
            arrayList1_DIY.add(i);
        }

        System.out.println("arrayList1_DIY size=" + arrayList1_DIY.size());

        int index_1 = arrayList1_DIY.get(0);
        System.out.println(index_1);

        int index_2 = arrayList1_DIY.get(1);
        System.out.println(index_2);

    }
}

输出结果

需要进行扩容操作!
旧容量=0
新容量=10
需要进行扩容操作!
旧容量=10
新容量=15
arrayList1_DIY size=11
0
1
总结获取元素功能(总结get方法)

调用get方法时,会先判断index是否是正溢出范围的,如果是就返回自定义的异常信息(这个range检查可有可无,只是为了自定义异常信息),如果不是,就调用elementData方法,返回指定位置的元素并做类型转换。

移除元素功能(remove方法)

讲到remove功能,DIY ArrayList和测试类的代码有所修改,

有两种移除方式一种是移除指定位置的元素,一种是移除value=某个值的所有元素。
这里先将通过指定位置移除元素,即remove(int index)

如下:

DIY ArrayList代码

package spring.boot.study.javasourcecode;

import java.util.Arrays;

public class ArrayList_DIY<E> {

    transient Object[] elementData;

    transient int modCount = 0;

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    private int size;

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

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

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList_DIY(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);
        }
    }

    public ArrayList_DIY() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    public void rangeCheck(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }

    @SuppressWarnings("unchecked")
    private E elementData(int index) {
        return (E) elementData[index];
    }

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

    public int size() {
        return size;
    }

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

    }

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0) {

            // FIXME
            if (ArrayListTest.TRACE && ArrayListTest.EXPANSION_TRACE) {
                System.out.println("需要进行扩容操作!");
            }

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

        // FIXME
        if (ArrayListTest.TRACE && ArrayListTest.EXPANSION_TRACE) {
            System.out.println("旧容量=" + oldCapacity);
            System.out.println("新容量=" + newCapacity);
        }

        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

    // remove功能,移除指定位置的元素
    public E remove(int index) {

        // 先做范围检查,可有可无,在一定条件下抛自定义的异常信息,方便追踪
        rangeCheck(index);

        modCount++; // modify次数+1
        E oldValue = (E) elementData[index];

        /**
         * 注意:下面说的”移动“,并非是真正的移动,其实用的是copy,copy之后把需要清空的位置清空,做到所谓的移动。
         * 下面的FIXME标签代码就是测试。了解了size和容器容量的话,这个不难掌握。
         *
         * 移除指定位置的元素后,需要判断是否要移动其它元素,去填充数组。
         * 比如:elementData数组的size是10,即有10个元素,移除的是数组最后一位元素,即第10位(index=9),
         * 这样就不需要移动其它元素了,直接就是elementData[9]=null,size-1;
         * 如果不是最后一位,就先移动index后的元素先位置都先前,即index后的元素统一左移,最后空出的就是elementData[9]
          */
        int numMoved = size - index - 1; // 得到需要移动元素的个数
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);

        // FIXME
        if (ArrayListTest.TRACE && ArrayListTest.REMOVE_TRACE) {

            size--; // 移除了一个size减少1

            System.out.println("移动后,数组的最后一位的值是" + elementData[size]);
            System.out.println("移动后,数组的最后倒数第二位的值是" + elementData[size - 1]);
            System.out.println("移动后,数组的倒数第三位的值是" + elementData[size - 2]);
        } else { // 不是测试,则用JDK源码
            elementData[--size] = null; // clear to let GC do its work
        }

        return oldValue;
    }

}

看看remove方法代码

    // remove功能,移除指定位置的元素
    public E remove(int index) {

        // 先做范围检查,可有可无,在一定条件下抛自定义的异常信息,方便追踪
        rangeCheck(index);

        modCount++; // modify次数+1
        E oldValue = (E) elementData[index];

        /**
         * 注意:下面说的”移动“,并非是真正的移动,其实用的是copy,copy之后把需要清空的位置清空,做到所谓的移动。
         * 下面的FIXME标签代码就是测试。了解了size和容器容量的话,这个不难掌握。
         *
         * 移除指定位置的元素后,需要判断是否要移动其它元素,去填充数组。
         * 比如:elementData数组的size是10,即有10个元素,移除的是数组最后一位元素,即第10位(index=9),
         * 这样就不需要移动其它元素了,直接就是elementData[9]=null,size-1;
         * 如果不是最后一位,就先移动index后的元素先位置都先前,即index后的元素统一左移,最后空出的就是elementData[9]
          */
        int numMoved = size - index - 1; // 得到需要移动元素的个数
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);

        // FIXME
        if (ArrayListTest.TRACE && ArrayListTest.REMOVE_TRACE) {

            size--; // 移除了一个size减少1

            System.out.println("移动后,数组的最后一位的值是" + elementData[size]);
            System.out.println("移动后,数组的最后倒数第二位的值是" + elementData[size - 1]);
            System.out.println("移动后,数组的倒数第三位的值是" + elementData[size - 2]);
        } else { // 不是测试,则用JDK源码
            elementData[--size] = null; // clear to let GC do its work
        }

        return oldValue;
    }

FIXME标签下的else是源码,加上FIXME是为了测试。
把下面代码替换就是源码

	// FIXME
        if (ArrayListTest.TRACE && ArrayListTest.REMOVE_TRACE) {

            size--; // 移除了一个size减少1

            System.out.println("移动后,数组的最后一位的值是" + elementData[size]);
            System.out.println("移动后,素质的最后倒数第二位的值是" + elementData[size - 1]);
            System.out.println("移动后,素质的倒数第三位的值是" + elementData[size - 2]);
        } else { // 不是测试,则用JDK源码
            elementData[--size] = null; // clear to let GC do its work
        }

替换为:

elementData[--size] = null; // clear to let GC do its work

remove方法,remove元素时会判断移除的元素位置是不是最后一个,如果不是,若移除该元素,则该元素后面的其它元素都得往前填补位置。源码里是先“移动”元素即该元素后面的其它元素往前填补位置,其实这个时候该元素已经不存在数组了(大家不要钻牛角尖,因为List是可重复)。

需要注意的是,这里说的“移动”并非真正移动,而是用copy的方式,然后再清空最后一位重复的元素,就做到所谓的移动。需要理清size和容器容量的概念以及区别。

remove方法不是用移动,而是用copy

remove(int index),如果index不是最后一位,它会把index位置的后一位元素copy到index位置,后两位元素copy到后一位位置…,把循环如此直到数组最后一个元素。但是这样尾部就会重复,所以需要将尾部的值置为null,这样垃圾回收器就能回收了。
elementData[--size] = null; // clear to let GC do its work这语句就是上面的意思。

还是画个图吧,如下:
在这里插入图片描述
copy对应的代码部分

        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

将ArrayListTest类中的REMOVE_TRACE设为false
public final static boolean REMOVE_TRACE = false;

测试移除指定位置元素功能(测试remove指定位置元素方法)
arrayList1_DIY size=11
移除元素前,index=7的值为:7
移除元素后,index=7的值为:8
总结移除指定位置元素功能(总结remove指定位置元素方法)

要了解size和容器容量的概念以及区别。
remove方法时通过copy指定index后元素放到前一位,直到最后一位元素完成copy。还要去掉最后一位元素,达到“移动”的效果。


版权声明:本文为weixin_42488909原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。