Java基础11 Java集合之ArrayList源码解析

  从上篇Java集合的初步认识文章中,我们了解到 Collection 是Java集合的一大鼻祖,Collection 接口中定义了集合类的基本操作方法。在了解ArrayList之前,我们先来看看它的祖先Collection。

Collection接口中的方法

源码中 Collection 的定义

public interface Collection<E> extends Iterable<E> {

Collection 接口中的方法

返回类型 方法 描述
int size() 返回集合中的元素的个数
boolean isEmpty() 若集合中不含有元素,返回true
boolean contains(Object o) 若集合中含有某指定元素,返回true
boolean containsAll(Collection<?> c) 若集合中包含指定集合中的所有元素,则返回true
Iterator<E> iterator() 返回集合中元素的迭代器
Object[] toArray() 把集合中元素放入数组中,返回数组
<T> T[] toArray(T[] a) 返回包含此集合中所有元素的数组;返回数组的运行时类型是指定的数组的运行时类型
boolean add(E e) 添加指定类型的元素到集合中
boolean addAll(Collection<? extends E> c) 将指定集合中的所有元素添加到这个集合
boolean remove(Object o) 移除集合中指定的元素
boolean removeAll(Collection<?> c) 移除此集合包含指定集合的所有元素
default boolean removeIf(Predicate<? super E> filter) 删除满足条件的元素(本接口已实现方法)
boolean retainAll(Collection<?> c) 仅保留包含在指定集合中的这个集合中的元素
void clear() 清空集合
boolean equals(Object o) 将指定的对象与此集合比较
int hashCode() 返回此集合的哈希值
default Spliterator<E> spliterator() 创建此集合中的元素的 Spliterator(本接口已实现方法)
default Stream<E> stream() 返回一个序列 Stream与集合的来源(本接口已实现方法)
default Stream<E> parallelStream() 返回一个可能并行 Stream与集合的来源(本接口已实现方法)

ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

  从源码中ArrayList的定义,我们可以看出ArrayList是AbstractList的一个子类,同时又实现了 List、RandomAccess、Cloneable、Serializable 接口,使其具有快速随机访问可克隆可序列化的功能。

ArrayList上层结构

ArrayList
  通过上面的图,我们可以清晰的了解ArrayList的上层结构,ArrayList是List接口的一大实现类,也是List接口的最常用的实现类,下面就来详细的分析ArrayList。

ArrayList的源码分析

成员变量
// 默认大小 10
private static final int DEFAULT_CAPACITY = 10;
// ArrayList 中实际元素的个数
private int size;
// ArrayList 存储元素的数组对象
transient Object[] elementData;
// 用于空实例的共享空数组实例(构造方法初始化对象是使用给定的大小)
private static final Object[] EMPTY_ELEMENTDATA = {};
// 共享的空数组实例用于默认大小的空实例(构造方法初始化对象时使用默认大小)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// ArrayList 默认最大的容量大小(设置Integer的最大值 - 8 是因为要空出8的大小给数组对象的 _length)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

  为什么初始化ArrayList对象时要分两个数组对象初始化呢?源代码是这样解释的 We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added是用来区分初始化ArrayList对象的方式是通过无参构造还是有参构造,这样做的目的是为了后面的扩容。

构造方法
// 无参构造方法,使用默认的空数组实例对象(大小为默认的大小 10)
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 有参构造方法,用于初始化ArrayList的大小,使用空数组实例对象
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);
    }
}
// 有参构造方法,为空的时候也是使用空数组实例对象;不为空时直接拷贝传入的对象元素放入到ArrayList中,大小为传入对象的大小
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

  上面的三种构造方法对应着三种的初始化场景,无参构造方法主要是用来初始化一个为空的ArrayList对象,底层使用的是静态的 DEFAULTCAPACITY_EMPTY_ELEMENTDATA Object对象实例数组;而另外两个有参构造方法目的是用来初始化用户想要大小的ArrayList对象,当用户给定的大小为 0 的时候,底层使用静态的 EMPTY_ELEMENTDATA Object对象实例数组;当这两种有参方法传入的参数大小不为 0 的时候,底层使用的是给定大小的动态的 Object 数组对象。这样做的目的是为了后面的扩容策略。

主要的方法源码解析
add(E e) 添加单个元素
// add 方法的主入口
public boolean add(E e) {
	// 主要解决AraayList大小问题的方法主入口
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 把传入的元素添加到ArrayList中
    elementData[size++] = e;
    return true;
}

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

// 判断传入的大小是否大于ArrayList的大小
private void ensureExplicitCapacity(int minCapacity) {
	// 每次操作都计数一次,该字段来源于ArrayList的父类AbstractList抽象类,主要作用是为了避免遍历的时候修改ArrayList对象
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
    	// ArrayList 的扩容的方法
        grow(minCapacity);
}
// 判断ArrayList的大小,若是无参方法创建的实例就取默认大小与传入大小的最大值,否则直接取传入值
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

  经过对ArrayList源代码的分析,我们可以知道每次添加元素之前都会确认ArrayList容量的大小,再去size++。在确认ArrayList容量大小的过程中,我们可以看到源代码是先去对容量大小参数minCapacity做一个粗略的判断,在方法 calculateCapacity中我们可以看到是做了分底层数组对象处理,若为DEFAULTCAPACITY_EMPTY_ELEMENTDATA(也就是无参构造函数实例化出来的默认的静态数组对象)对象,则取默认大小(10)与传入大小的最大值,若是有参构造实例化出来的对象,则直接取传入的minCapacity值;然后在ensureExplicitCapacity方法中去做是否进行扩容操作的判断。扩容的实际操作是在 grow 方法中,我们接下来看看ArrayList是怎样扩容的。

grow(int minCapacity) 扩容操作
private void grow(int minCapacity) {
    // overflow-conscious code
    // 记录原ArrayList的容量大小
    int oldCapacity = elementData.length;
    // 使用为位运算的方式进行扩容,扩为原来大小的 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 判断扩容后的大小与传入的大小比较,若还小,就直接让新容量大小为传入的大小
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 若扩容后的大小超过 Integer.MAX_VALUE - 8 的大小,则使用 Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    // 将数据拷贝到扩容后的 ArrayList 中
    elementData = Arrays.copyOf(elementData, newCapacity);
}
// 取 Integer.MAX_VALUE 值做ArrayList的容量大小
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

  对ArrayList的扩容方法的源码分析,我们可以了解到ArrayList的默认一次扩容大小为 1.5 倍原来的容量大小;源码对扩容后的大小又做了进一步的判断,若扩容后的大小还不满足用户的需求,则直接把ArrayList的容量大小设为用户需要的大小(传入的大小),最后对最终的容量大小又进一步判断,判断是否超过 Integer.MAX_VALUE - 8 = 2147483639 的大小,超过则直接给 Integer.MAX_VALUE = 2147483647 的大小,若还超过,那么对不起,直接报错O(∩_∩)O哈哈~。
  为什么ArrayList的最大容量大小设置为 Integer.MAX_VALUE - 8 ?关于这个问题,细心地看官应该能注意到,我在列出ArrayList成员变量的时候简单的解释了这个问题;其实也不难理解,数组是一个对象,内存在存储对象的时候,不但要存储对象中的元素,还要存储对象的头信息,对象的头信息不可能会超过 8 ,所以ArrayList的默认最大的容量就设置为 Integer.MAX_VALUE - 8;但后面又为什么把Integer的最大值给ArrayList的容量呢?这里其实是想最大限度的提高内存的使用率,对象的头信息不可能超过 8 ,也就是说对象的头信息有可能只需要占 4 的大小就可以了,有进一步占据空间的可能性。

add(int index, E element) 指定位置上插入一个元素
public void add(int index, E element) {
	// 对传入的下标进行有效检测判断
    rangeCheckForAdd(index);
	// 尝试改变 ArrayList 容量的大小
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 调用底层的数组拷贝方法,空出下标为 index 的位置
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 把元素添加到指定下标的位置
    elementData[index] = element;
    // ArrayList 的实际元素的大小 +1
    size++;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
addAll(Collection<? extends E> c) 添加一个集合到ArrayList中
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}
addAll(int index, Collection<? extends E> c) 从ArrayList指定下标开始添加一个集合元素
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    // 尝试改变 ArrayList 容量的大小
    ensureCapacityInternal(size + numNew);  // Increments modCount

    int numMoved = size - index;
    if (numMoved > 0)
    	// 腾出空间来存储传入的集合元素
        System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
	// 将传入的集合元素添加到 ArrayList 指定位置
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

分析完ArrayList的添加操作,我们解下来分析下ArrayList的删除操作。

remove(int index) 移除指定下标的元素
public E remove(int index) {
	// 检测给定的下标是否在规定的范围内
    rangeCheck(index);
	// 修改ArrayList操作的计数器
    modCount++;
    // 取出指定下标的元素,用于返回
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    // 指定下标不是ArrayList的最后一个元素的下标时,调用底层数组的拷贝方法
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    // 将原先ArrayList的最后以一个元素设置为null,便于被回收,保证不浪费内存空间
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

private void rangeCheck(int index) {
	// 只做了向上的越界,没有做向下的越界的原因在于给定下标index < 0,ArrayList在调用底层数组拷贝时,不会做元素的移动
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
    return (E) elementData[index];
}
removeRange(int fromIndex, int toIndex) 移除一个范围的元素
protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);

    // clear to let GC do its work
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}
remove(Object o) 移除指定的元素
public boolean remove(Object o) {
	// 根据传入的对象,移除第一匹配到的元素
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
fastRemove(int index) 快速移除
// 快速移除方法,所谓开始就是不做任何判断,只专注做移除元素的业务操作,其限制判断在别的方法做了限制,保证了其传入的 index 的有效性
private void fastRemove(int index) {
    modCount++;
    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
}
removeAll(Collection<?> c) 移除与指定集合元素相匹配的元素
public boolean removeAll(Collection<?> c) {
	// 判断传入的集合是否为空,调用 Object 类的不为空的方法
    Objects.requireNonNull(c);
    // 批量移除
    return batchRemove(c, false);
}

private boolean batchRemove(Collection<?> c, boolean complement) {
	// 定义不可变的变量指向外部的 elementData 的内存地址
    final Object[] elementData = this.elementData;
    // r 表示读. w表示写
    int r = 0, w = 0;
    boolean modified = false;
    try {
    	// for 循环保留与传入的集合c中的元素不匹配的数据,存储在elementData(方法里的变量) 
        for (; r < size; r++)
        	// complement 是一个标记, true表示保留, false表示移除
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        // 抛出异常的情况下,移除抛异常之前的与传入参数的元素相匹配的数据
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        // 没有抛异常,正常遍历结束,移除 w 后面的所有元素(置null)
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}
retainAll(Collection<?> c) 保留与指定集合元素相匹配的元素
public boolean retainAll(Collection<?> c) {
    Objects.requireNonNull(c);
    // true,保留与之相匹配的元素,方法在上面代码框内
    return batchRemove(c, true);
}

   通过分析 removeAll 的源代码,我们ArrayList的方法设计非常解耦、通用,它通过一个complement标记,省略了极大的重复代码,这个标记被 removeAllretainAll 两个方法所使用,前者是移除与传参集合中元素相匹配的数据,后者是保留与传参集合中元素相匹配的数据。

clear() 清空所有
public void clear() {
   modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}
removeIf(Predicate<? super E> filter) 移除满足条件的元素
@Override
public boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    // figure out which elements are to be removed
    // any exception thrown from the filter predicate at this stage
    // will leave the collection unmodified
    int removeCount = 0;
    // 使用BitSet对象记录要移除元素的下标
    final BitSet removeSet = new BitSet(size);
    // 用于判别对ArrayList的改动次数是否一致,主要就是防止多线程修改
    final int expectedModCount = modCount;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        @SuppressWarnings("unchecked")
        final E element = (E) elementData[i];
        // 若集合元素满足指定的规则,则将BitSet中对应下标位置的值设为true
        if (filter.test(element)) {
            removeSet.set(i);
            removeCount++;
        }
    }
    // 判断其他线程是否对该对象集合做了改动
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }

    // shift surviving elements left over the spaces left by removed elements
    final boolean anyToRemove = removeCount > 0;
    if (anyToRemove) {
    	// newSize 为过滤之后集合的实际大小
        final int newSize = size - removeCount;
        for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
        	// nextClearBit 返回设置为false的第一个位置的索引(下标)
            i = removeSet.nextClearBit(i);
            // 保存不符合断言条件的元素,变相地移除了满足条件的元素
            elementData[j] = elementData[i];
        }
        // 将newSize后面的元素全部置null
        for (int k=newSize; k < size; k++) {
            elementData[k] = null;  // Let gc do its work
        }
        this.size = newSize;
        // 判断其他线程是否对该对象集合做了改动
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

    return anyToRemove;
}

   removeIf方法是对Collection接口方法的重写,源码也说了在执行该方法的过程中,抛出任何异常都会中断操作,不会对ArrayList做任何变更性操作,方法是根据过滤条件去移除满足条件的数据,里面运用到 BitSet 这个在后续讲解Set系列的时候回去详细的讲解,这里只针对运用到的方法做简单地讲解。removeIf 方法的核心就在于对BitSet的运用,分别用到了set() 和 nextClearBit() 方法,set()方法是将满足过滤条件的下标设置为true,nextCLearBit()方法是取出为false的下标。
举个例子:

ArrayList集合元素下标情况:[0, 1, 2, 3, 4, 5, 6]
满足断言过滤条件的下标情况:[3, 5, 6]
for循环调用nextCLearBit()得出不满足过滤条件的下标:[0, 1, 2, 4]

  通过分析ArrayList的移除操作的源代码,我们可以知道ArrayList的移除操作其实是很简单的,就是普通的数组移除元素的思想,先对传入的参数做有效性判定,然后就是找到要删除元素的下标,再调用底层的数组相应拷贝方法,移动ArrayList数组,最后就做相应的返回。

replaceAll(UnaryOperator operator) 替换满足条件的元素

从方法参数可以看出参数是一个函数式接口,这就意味着参数可以使用Lambda表达式

@Override
@SuppressWarnings("unchecked")
public void replaceAll(UnaryOperator<E> operator) {
	// 不为空校验
    Objects.requireNonNull(operator);
    // List结构被修改的次数
    final int expectedModCount = modCount;
    final int size = this.size;
    // 没有被多线程修改结构
    for (int i=0; modCount == expectedModCount && i < size; i++) {		
    	// 按照 lamdba 表达式做相应的操作
        elementData[i] = operator.apply((E) elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

// UnaryOperator 接口
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    /**
     * Returns a unary operator that always returns its input argument.
     *
     * @param <T> the type of the input and output of the operator
     * @return a unary operator that always returns its input argument
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}
// List 里面的方法
default void replaceAll(UnaryOperator<E> operator) {
    Objects.requireNonNull(operator);
    final ListIterator<E> li = this.listIterator();
    while (li.hasNext()) {
        li.set(operator.apply(li.next()));
    }
}

示例:

@Test
public void replaceTest(){
    List<Integer> list = createData();  // 生成 0 - 9 的数字List
    list.iterator().forEachRemaining(i -> System.out.print(String.valueOf(i) + "、"));
    // 把元素为 3 的改为 13
    list.replaceAll(i -> i == 3 ? 13 : i);
    System.out.println();
    list.iterator().forEachRemaining(i -> System.out.print(String.valueOf(i) + "、"));
}

输出显示:

0、1、2、3、4、5、6、7、8、9、
0、1、2、13、4、5、6、7、8、9、
sort(Comparator<? super E> c) 按条件排序

该方法的参数也是一个函数式接口,条件就可以通过这个接口去设定

@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

示例:

@Test
public void sortTest(){
    List<Integer> list = createData();
    list.iterator().forEachRemaining(i -> System.out.print(String.valueOf(i) + "、"));
    System.out.println();
    list.sort((a, b) -> b.compareTo(a)); // 置为倒序
    list.iterator().forEachRemaining(i -> System.out.print(String.valueOf(i) + "、"));
}

输出结果:

0、1、2、3、4、5、6、7、8、9、
9、8、7、6、5、4、3、2、1、0、
iterator 迭代器
iterator() 用于迭代输出
public Iterator<E> iterator() {
    return new Itr();
}

  从源码中,可以看出,调用 iterator 方法,其实就是new 出来一个内部类对象,我们接下来就来看看此 Itr 内部类。

ArrayList 的内部类

Itr 内部类
private class Itr implements Iterator<E> {
成员变量
// 用于指向下一个要返回的值
int cursor;       // index of next element to return
// 用于返回值的下标(在实际操作中,会返回下标[lastRet + 1]的值)
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
构造方法
Itr() {}
hasNext() 判断后面是否还存在元素
public boolean hasNext() {
    return cursor != size;
}
next() 取出元素
public E next() {
	// 检测是否有其他线程对此对象进行操作
    checkForComodification();
    int i = cursor;
    // 判断cursor后面是否还有元素
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    // 进一步保证不会越界
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    // 返回curson移动之前下标对应的元素,同时把lastRet指向curson移动之前下标
    return (E) elementData[lastRet = i];
}

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

   这里解释的可能不太好,大家意会下,就是两个指针,一前一后的指向相邻的两个元素。

remove() 移除元素
public void remove() {
	// 对 lastRet 做判断,防止没有经过 next 就开始删除元素
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
    	// 调用外部类的 remove 方法,直接把指定下标(lastRet)后面的元素网前移动
        ArrayList.this.remove(lastRet);
        // 将cursor指向lastRet,因为lastRet后面所有的元素都往前移动一位,cursor的值也就必须往前移
        cursor = lastRet;
        // 将lastRet的值重新设置为起始值,防止在遍历的过程恶意进行无限循环删除
        lastRet = -1;
        // 将期望修改值重新赋值为修改值,目的就是为了能够进行多次操作,因为经过一次删除操作,modCount加了1,在next()方法开始之前,就对这两个进行了一次相等校验,不重新赋值,下一次调用next就会报错,这将会和普通的for一样,在遍历的过程中不能对ArrayList进行修改性操作
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

  通过对内部类Itr的 hasNextnextremove 三个方法的源代码的解读,我们终于知道为什么iterator迭代器支持遍历的同时对ArrayList对象进行修改性的操作,原因就在于内部类 remove 方法的 expectedModCount = modCount 语句,它可以使得删除操作对修改次数的校验不会抛异常。

forEachRemaining(Consumer<? super E> consumer) 迭代器遍历
@Override
@SuppressWarnings("unchecked")
// 该方法重写了 Iterator 接口中的方法,思维逻辑很简单,我这就不过多的解释
public void forEachRemaining(Consumer<? super E> consumer) {
    Objects.requireNonNull(consumer);
    final int size = ArrayList.this.size;
    int i = cursor;
    if (i >= size) {
        return;
    }
    final Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length) {
        throw new ConcurrentModificationException();
    }
    while (i != size && modCount == expectedModCount) {
        consumer.accept((E) elementData[i++]);
    }
    // update once at end of iteration to reduce heap write traffic
    cursor = i;
    lastRet = i - 1;
    checkForComodification();
}

  该方法支持流操作,可以对元素做用户自己想做的操作,很灵活,建议大家能多使用这个方法。

ListItr 内部类

public ListIterator<E> listIterator() {
    return new ListItr(0);
}
private class ListItr extends Itr implements ListIterator<E> {

  该内部类继承了 Itr 内部类,所以它具备 Itr 所有操作;但由于 Itr 类只能做遍历中删除,不能做遍历中修改和插入,为了弥补这一缺陷,就出现了 ListItr 内部类。

构造方法
ListItr(int index) {
    super();
    cursor = index;
}
三个基础方法
// 判断当前元素前面是否有元素
public boolean hasPrevious() {
    return cursor != 0;
}
// 获取当前的下标
public int nextIndex() {
    return cursor;
}
// 获取前一个数的下标
public int previousIndex() {
    return cursor - 1;
}
操作方法
previous() 获取前一个元素
public E previous() {
	// 检验期望变更次数与变更次数是否相等,防止多线程操作
    checkForComodification();
    // 前元素下标
    int i = cursor - 1;
    if (i < 0)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i;
    // 返回前一个元素的值
    return (E) elementData[lastRet = i];
}
set(E e) 将指定下标的值设置为参数e
public void set(E e) {
	// 这个判断就限制了该方法必须经过一次next或previous方法且lastRet 大于等于0
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.set(lastRet, e);
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}
add(E e) 添加一个元素
public void add(E e) {
    checkForComodification();
	// 思想与remove是一样的,这里只不过是添加,所以应该cursor往后移动
    try {
        int i = cursor;
        ArrayList.this.add(i, e);
        cursor = i + 1;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

SubList 内部类

生成方法:

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

static void subListRangeCheck(int fromIndex, int toIndex, int size) {
   if (fromIndex < 0)
       throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
   if (toIndex > size)
       throw new IndexOutOfBoundsException("toIndex = " + toIndex);
   if (fromIndex > toIndex)
       throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                          ") > toIndex(" + toIndex + ")");
}
private class SubList extends AbstractList<E> implements RandomAccess {
成员变量
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
构造函数
SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) {
	// 这里指的父类不是继承的那种关系,是指外部类,说父类只是便于说明
    this.parent = parent;	// 指向父类
    this.parentOffset = fromIndex;	// 父类的偏移量
    this.offset = offset + fromIndex;	// 子类的偏移量
    this.size = toIndex - fromIndex;	// 子类的实际大小
    this.modCount = ArrayList.this.modCount;	// 修改次数
}

  观察这个构造方法,这个子List不是单独生成的,它是指向父List的,所以它内部的方法操作的结果必定会影响到父List的元素,反之亦然。

方法

  SubList的内部类中的方法与外部类ArrayList的方法的实现思想是一样的,只不过是添加了一些偏移量而已;由于有太多重复的,前面分析ArrayList的的时候大部分都已经说过,只要能理解前面的,这里的也自然能理解,我就一一在解读一遍,只挑几个方法来解读。
  重要:SubList内部类要慎用,尽量不要对SubList做修改操作。

add(int index, E e) 在指定位置添加一个元素
public void add(int index, E e) {
    rangeCheckForAdd(index);
    checkForComodification();
    // 直接对父List的指定位置添加一个元素
    parent.add(parentOffset + index, e);
    this.modCount = parent.modCount;
    this.size++;
}
remove(int index) 移除指定位置的元素
public E remove(int index) {
    rangeCheck(index);
    checkForComodification();
    // 直接移除父List指定位置的一个元素
    E result = parent.remove(parentOffset + index);
    this.modCount = parent.modCount;
    this.size--;
    return result;
}

  从添加和删除中,我们可以看到所有修改操作,都会直接去操作父List,所以会直接影响到父List的数据结构。SubList内部类里面也有 iterator 方法,该方法是创建一个 ListIterator 内部类(SubList里面的内部类),器实现思想都是一样的,我这里就不再去解析了,有兴趣的可以自己去看源码,很好理解的,一看就能懂得。

iterator()
public Iterator<E> iterator() {
    return listIterator();
}

public ListIterator<E> listIterator(final int index) {
    checkForComodification();
    rangeCheckForAdd(index);
    final int offset = this.offset;

    return new ListIterator<E>() { ... }

总结

  1. ArrayList底层结结构为 Object 数组,其默认容量为 10,默认扩容大小为 1.5 倍,一次扩容不成功则 newCapacity > MAX_ARRAY_SIZE ? Integer.MAX_VALUE : MAX_ARRAY_SIZE
  2. ArrayList 有三种初始化对象,分别是无参构造产生的静态Object数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA ,有参构造参数是空数据产生的静态Object数组 EMPTY_ELEMENTDATA ,有参构造参数是有数据产生的动态 Object 数组。
  3. ArrayList 在for循环中不能多次修改操作,可以在Iterator里面做多次修改操作。
  4. ArrayList 有四大 IteratorListIteratorSubListArrayListSpliterator 内部类。Iterator 只支持向后遍历,ListIterator支持双向遍历, SubList 要慎用,ArrayListSpliterator 把List均匀分割成几部分。
  5. ArrayList本质是数组,所以它的查找速率很快,而插入删除,性能会比较低。
原文地址:https://www.cnblogs.com/sophia-show/p/13684288.html