ArrayList源码复习2022/3/23

188 阅读10分钟

介绍

ArrayListList接口的可调整大小的数组实现。 实现所有可选的列表操作,并允许所有元素,包括null。 除了实现List接口外,该类还提供了一些方法来操作用于在内部存储列表的数组的大小。 (这个类大致相当于Vector,除了它是不同步的。)  

sizeisEmptygetsetiteratorlistIterator操作以常量时间运行。 添加操作的运行时间为平摊常数时间,即添加n个元素需要O(n)时间。 所有其他操作都在线性时间内运行(粗略地说)。 与LinkedList实现相比,这个常数因子较低。  

每个ArrayList实例都有一个容量。 容量是用于存储列表中元素的数组的大小。 它总是至少和列表大小一样大。 当元素被添加到数组列表时,它的容量会自动增长。 除了添加元素的摊销时间成本为常数这一事实外,增长策略的细节没有被指定。  

应用程序可以在使用ensureCapacity操作添加大量元素之前增加ArrayList实例的容量。 这可能会减少增量重新分配的数量

注意,这个实现不是同步的。 如果多个线程同时访问一个ArrayList实例,并且至少有一个线程在结构上修改了列表,则必须在外部对其进行同步。 (结构修改是添加或删除一个或多个元素,或显式调整后置数组的任何操作; 仅仅设置元素的值并不是结构修改。) 这通常是通过对自然封装列表的对象进行同步来实现的。 如果不存在这样的对象,则应该使用Collections.synchronizedList 方法。 这最好在创建时完成,以防止意外的不同步访问列表

这个类的iteratorlistIterator方法返回的迭代器是快速失败的:如果在迭代器创建后的任何时候,列表的结构被修改了,除了通过迭代器自己的remove或add方法外,任何方式都可以,迭代器将抛出ConcurrentModificationException。 因此,在面对并发修改时,迭代器会快速而干净地失败,而不是冒着在未来未知时间出现任意、不确定性行为的风险。  

请注意,迭代器的快速失效行为不能得到保证,因为一般来说,在存在非同步并发修改时,不可能做出任何硬保证。 快速失败迭代器尽最大努力抛出ConcurrentModificationException异常。 因此,编写依赖于这个异常来保证其正确性的程序是错误的:迭代器的快速失效行为应该只用于检测错误。

追踪源码

ArrayList继承AbstractList,实现List,RandomAccess,CloneableSerializable接口

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

类成员变量


/**
 * 默认初始容量
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 用于空实例的共享空数组实例。
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
  共享空数组实例,用于默认大小的空实例。
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 *数组缓冲区,存储数组列表中的元素。任何空的数组列表与elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 将在添加第一个元素时扩展为DEFAULT_CAPACITY。
 */
transient Object[] elementData; 

/**
 * 数组大小
 */
private int size;

构造函数

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);
    }
}
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

添加元素

/**
 * 将指定的元素追加到该列表的末尾,添加成功返回true
 */
boolean add(E e);
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) {
// 如果elementData此时为空的话 直接使用默认初始化大小10,否则使用minCapacity
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
// 修改次数+1
    modCount++; 

    // 如果minCapacity大于此时数据长度需要扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
private void grow(int minCapacity) {
    // 原数组长度
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果  newCapacity 大于  MAX_ARRAY_SIZE 最大整数值 进入 hugeCapacity 该方法返回最大容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 开始copy数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}
// Arrays.java
@SuppressWarnings("unchecked")
// 传入原数组和新的数组长度
public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    // 判断类型如果Object类型生成new Object[newLength]否则生成相对应类型数组
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
     /original 原数组
      *0 从元数据的起始位置开始
    *copy 目标数组
    *0 目标数组的开始起始位置
   / Math.min(original.length, newLength)要copy的数组的长度
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

删除指定下标元素

移除列表中指定位置的元素,将后面的所有元素向左移动(从它们的下标中减去1)。返回从列表中删除的元素。

E remove(int index);
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; // 清除最后一位
    
    return oldValue;
}

删除指定元素

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;
}
/*
 *跳过边界检查且不返回已删除值的私有remove方法。 不需要判断是否数组越界
 */
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
}

查找指定下标元素

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

关于ensureCapacity

// 必要时,增加这个Arraylist实例的容量,以确保它至少可以容纳最小容量参数指定的元素数量。参数:minCapacity—所需的最小容量
public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

ConcurrentModificationException

当我们迭代一个ArrayList或者HashMap时,如果尝试对集合做一些修改操作(例如删除元素),可能会抛出java.util.ConcurrentModificationException的异常。比如

/**
 * @author tan
 */

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("word");
        list.add("hello");
        list.add("java");
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            if (it.next().equals("hello")) {
                list.remove(it.next());
            }
        }
    }
}

list.iterator()就是使用的Itr

private class Itr implements Iterator<E> {
    int cursor;       // 要访问的元素索引
    int lastRet = -1; // 上一个访问元素的索引
    int expectedModCount = modCount; //该变量会初始化和`modCount`相等

    Itr() {}
  //判断是否还有下一个元素
    public boolean hasNext() {
        return cursor != size;
    }
  //取出下一个元素
    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    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();
    }

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

里面一段代码

// 该函数判断此时的modCount是否等于当前修改值,如果不是抛出异常
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

如何在迭代集合时删除集合的元素

1. 单线程环境

查看源码可以知道Iterator里面存在一个remove,上述代码改为

/**
 * @author tan
 */

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("word");
        list.add("hello");
        list.add("java");
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            if (it.next().equals("hello")) {
                it.remove();
            }
        }
        list.forEach(item->System.out.println(item));
    }
}

输出成功it.remove()实质也是调用了ArrayList中的remove,但增加了expectedModCount = modCount;保证了不会抛出java.util.ConcurrentModificationException异常。

但是,这个办法的有两个弊端

1.只能进行remove操作,add、clear等Itr中没有。

2.而且只适用单线程环境

2. 多线程环境

/**
 * @author tan
 */

public class Main  {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("word");
        list.add("hello");
        list.add("java");
        new Thread() {
            @Override
            public void run() {
                Iterator<String> iterator = list.iterator();

                while (iterator.hasNext()) {
                    System.out.println(Thread.currentThread().getName() + ":"
                            + iterator.next());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();

        new Thread() {
            @Override
            public synchronized void run() {
                Iterator<String> iterator = list.iterator();

                while (iterator.hasNext()) {
                    String element = iterator.next();
                    System.out.println(Thread.currentThread().getName() + ":"
                            + element);
                    if (element.equals("hello")) {
                        iterator.remove();
                    }
                }
            };
        }.start();

    }
}

将代码修改为多线程,依旧报错,因为一个线程修改modCount之后导致另外一个线程迭代时modCount与该迭代器的expectedModCount不相等。

此时有两个办法:

1.迭代前加锁,解决了多线程问题,但还是不能进行迭代add、clear等操作

/**
 * @author tan
 */

public class Main  {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("word");
        list.add("hello");
        list.add("java");
        new Thread() {
            @Override
            public void run() {
                Iterator<String> iterator = list.iterator();
                synchronized (list) {
                    while (iterator.hasNext()) {
                        System.out.println(Thread.currentThread().getName() + ":"
                                + iterator.next());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
        }.start();

        new Thread() {
            @Override
            public synchronized void run() {
                Iterator<String> iterator = list.iterator();
                synchronized (list) {
                    while (iterator.hasNext()) {
                        String element = iterator.next();
                        System.out.println(Thread.currentThread().getName() + ":"
                                + element);
                        if (element.equals("hello")) {
                            iterator.remove();
                        }
                    }
                }
            };
        }.start();

    }
}
  1. 采用CopyOnWriteArrayList,解决了多线程问题,同时可以add、clear等操作 CopyOnWriteArrayList也是一个线程安全的ArrayList,其实现原理在于,每次add,remove等所有的操作都是重新创建一个新的数组,再把引用指向新的数组。

fail-fast 机制

fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出 ConcurrentModificationException异常。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。fail-fast机制就是为了防止多线程修改集合造成并发问题的机制,之所以有modCount这个成员变量,就是为了辨别多线程修改集合时出现的错误

源码中关于modCount的注释翻译:

这个列表在结构上被修改的次数。结构修改是指改变列表的大小,或者以一种可能产生不正确结果的方式扰乱列表。该字段由iterator和listIterator方法返回的迭代器和列表迭代器实现使用。如果该字段的值意外更改,迭代器(或列表迭代器)将抛出ConcurrentModificationException,以响应下一个、删除、前一个、设置或添加操作。这提供了快速失效行为,而不是在迭代过程中面对并发修改时的不确定性行为。子类使用此字段是可选的。如果子类希望提供快速失败迭代器(和列表迭代器),那么它只需在它的add (int, E)和remove (int)方法中增加这个字段(以及它覆盖的导致列表结构修改的任何其他方法)。单个调用add (int, E)或remove (int)必须向该字段添加最多一个,否则迭代器(和列表迭代器)将抛出虚假的concurrentmodificationexception。如果一个实现不希望提供快速失败迭代器,这个字段可以被忽略。

ListIterator

ListIterator是一个功能更加强大的, 它继承于Iterator接口,只能用于各种List类型的访问。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator, 还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。

/**
 * @author tan
 */

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("word");
        list.add("hello");
        list.add("java");
        Iterator<String> it = list.listIterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

    }
}
// 返回列表中元素的列表迭代器(按正确的顺序)。返回:列表中元素的列表迭代器(按正确的顺序)
ListIterator<E> listIterator();
public ListIterator<E> listIterator() {
    return new ListItr(0);
}
private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor - 1;
    }

    @SuppressWarnings("unchecked")
    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];
    }

    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

(1)双向移动(向前/向后遍历).

(2)产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引.

(3)可以使用set()方法替换它访问过的最后一个元素.

(4)可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一个元素.

Iterator和ListIterator区别

(1)ListIterator有add()方法,可以向List中添加对象,而Iterator不能 (2)ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。 (3)ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。 (4)都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。