JDK 8 - ArrayList

165 阅读17分钟

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

size、isEmpty、get、set、iterator 和 listIterator 操作在常量时间内运行。 add 操作在 amortized constant time (平摊常数时间)内运行,即添加 n 个元素需要 O(n) 时间。所有其他操作都在线性时间内运行(在大体上讲)。与 LinkedList 实现相比,该常量系数较低。

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

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

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

该类的 iterator 和 listIterator 方法返回的迭代器是快速失败(fail-fast) 的:如果在迭代器创建后的任何时间对列表结构进行了修改,除了通过使用迭代器自己的 remove 或 add 方法,迭代器将抛出一个ConcurrentModificationException。因此,面对并发修改时,迭代器会快速而干净地失败,而不是在未来不确定的时间里冒任意的、不确定的行为的风险。

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

属性说明
数据结构数组
容量自动扩容、回收
是否允许null允许 null 值
时间复杂度size、isEmpty、get、set、iterator 和 listIterator 方法都以固定时间运行,时间复杂度为 O(1)。add和 remove 方法需要 O(n) 时间。与用于 LinkedList 实现的常数因子相比,此实现的常数因子较低;
是否同步不是同步的
迭代器fail-fast 迭代器

构造器

public ArrayList(int initialCapacity):构造一个具有指定初始容量的空列表。

public ArrayList():构造一个初始容量为 10 的空列表。

public ArrayList(Collection<? extends E> c):构造一个包含指定集合元素的列表,按照集合迭代器返回元素的顺序。

字段

int modCount:在父类AbstractList上,定义了modCount 属性,用于记录数组修改的次数。通过这个字段,实现了迭代器中 fail-fast 的行为。

private int size:数组元素大小。

方法

以下方法中,加粗的为 ArrayList 中新添的方法(省略了基类中的部分接口):

  • 通过索引访问

    E get(int index):通过索引查找元素的效率是很高的。 E set(int index, E element) void add(int index, E element) E remove(int index)

  • 查询操作

    int size() boolean isEmpty() boolean contains(Object o)

  • 搜索操作

    int indexOf(Object o) int lastIndexOf(Object o)

  • 修改操作

    boolean add(E e) boolean remove(Object o) boolean addAll(Collection<? extends E> c) boolean addAll(int index, Collection<? extends E> c) boolean removeAll(Collection<?> c) boolean retainAll(Collection<?> c) void removeRange(int fromIndex, int toIndex) boolean removeIf(Predicate<? super E> filter) void replaceAll(UnaryOperator<E> operator) void sort(Comparator<? super E> c) void clear():从该列表中删除所有元素。该调用返回后,该列表将为空。(这个操作不改变列表的数组容量大小

  • 迭代器

    Iterator<E> iterator() ListIterator<E> listIterator() ListIterator<E> listIterator(int index) Spliterator<E> spliterator()

  • 遍历

    void forEach(Consumer<? super E> action)

  • 视图

    List<E> subList(int fromIndex, int toIndex):返回一个视图,在这个视图上的操作会影响数据本身。

  • 拷贝

    Object clone():返回这个ArrayList实例的浅拷贝(元素本身不会被复制)。使用了 Arrays.copyOf(elementData, size); 操作。

  • 数组与 List 的桥梁

    Object[] toArray() <T> T[] toArray(T[] a)

  • 容量管理

    void trimToSize():将这个ArrayList实例的数组容量调整为列表的当前大小。使用的是Arrays.copyOf(elementData, size) 方法。 void ensureCapacity(int minCapacity):如果有必要,增加这个 ArrayList 实例的容量,以确保它至少能容纳由最小容量参数指定的元素数量。

源码

ArrayList 整体结构

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /* 默认初始容量 */
    private static final int DEFAULT_CAPACITY = 10;
    /**
     * 要分配的数组的最大大小
     * 有些虚拟机在数组中保留了一些标题字
     * 尝试分配更大的数组可能导致 OutOfMemoryError:Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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

    /**
     * 实际存储元素的数组
     * 注意到这里的访问控制是 non-private 的,并且使用了 transient 修饰
     * 
     * non-private to simplify nested class access
     (非私有,以简化嵌套类访问)
     */
    transient Object[] elementData;
    /* 数组元素大小 */
    private int size;
}

ArrayList 类图

ArrayList - 0 - 类图.png

  • 实现了 RandomAccess、Cloneable、Serializable 接口,可以随机访问、克隆、序列化|反序列化。
  • 实现了 List 接口,是 List 的实现之一;实现了 Collection 接口,是 Java 集合框架的成员之一。
  • 实现了 Iterable 接口,可以使用 for-each 迭代。

构造器

/**
 * 构造一个初始容量为10的空列(虽然在当前 elementData 中是一个空数组,但是在新增一个元素前,会先将容量扩为 10)。
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 构造一个具有指定初始容量的空列表。
 */
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);
    }
}

/**
 * 构造一个包含指定集合元素的列表,按照集合迭代器返回元素的顺序
 * 在这个里面,为了防止 c.toArray() 方法不正确的执行,导致没有返回 Object[],特殊做了处理
 * 不管传进来的是什么类型的数组,都会转为 Object[]
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray 可能不正确的,不返回 Object[]
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 替换为空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

在以上 3 种构造器方法中,分别对 elementData 赋予了不同的值 EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA 或者具体的数组:

  • 当使用无参构造器 ArrayList() 时,elementData 的值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
  • 当指定容量为 0,或者使用空集合创建时,elementData 的值为 EMPTY_ELEMENTDATA。

对于上述构造器,仅仅是申请了数组大小,对 size 依旧是 0;如果紧接着使用 set() 更改数组内元素,则会引发数组索引越界异常(这是因为判断是否越界是根据 size 进行的)!

部分方法

增删逻辑

都说 ArrayList 的增删是很慢的,接下来看看为什么慢:

  1. 新增

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public void add(int index, E element) {
        rangeCheckForAdd(index);
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    

    对于新增位置之后的元素都要进行复制;每次操作之前都会进行扩容检测,如果进行扩容,那速度将会更慢。

  2. 删除

    // 删除指定位置的元素
    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 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;
    }
    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
    }
    // 清空 list 列表
    public void clear() {
        modCount++;
    
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
    
        size = 0;
    }
    

    对于删除,都要讲删除位置之后的元素进行复制。

ArrayList 容量管理机制

new ArrayList<>().add("Hello"); 为例分析 ArrayList 的扩容机制:

1)对于构造器中的 elementData:

  • 如果 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 则将会将元素扩充为 10;
  • 如果 elementData == EMPTY_ELEMENTDATA 则将会将元素扩充为 1;

2)通常情况新容量是原来容量的 1.5 倍;如果 原容量的 1.5 倍 < minCapacity 那么就扩容到 minCapacity;(那是不是说,每次扩容最小扩容 1.5 倍?!)

3)特殊情况会扩容到 Integer.MAX_VALUE(这个操作可能引发 OutOfMemoryError)

4)add、addAll、readObject 等操作都会使用 ensureCapacityInternal() 进行扩容;

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

private void ensureCapacityInternal(int minCapacity) {
    // 如果该实例是通过使用无参构造器 ArrayList() 创建的,第一次扩充容量为 10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

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

    // "如果要扩容的容量 > 当前容量时" 才进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity) {
    // 具体的扩容策略
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); /* new = old * 1.5 */
    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);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow:已经超出 int 的范围了
        throw new OutOfMemoryError();
    // 如果要扩充的容量大于 MAX_ARRAY_SIZE 时,将返回 Integer.MAX_VALUE。
    // 注意:这时可能抛出 OutOfMemoryError
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

在扩容的时候,老版本的 jdk 和 8 以后的版本是有区别的,8 之后的效率更高了,采用了位运算,右移一位,其实就是除以 2 这个操作。

写代码验证一下这个扩容机制:

public class ListGrowTest {
    public static void main(String[] args) throws Exception {
        ArrayList defaultCapacity = new ArrayList();
        System.out.println("new ArrayList() 初始化长度:" + getElementData(defaultCapacity));
        defaultCapacity.add(1);
        System.out.println("new ArrayList().add() 后长度:" + getElementData(defaultCapacity));

        System.out.println("--- --- ---");
        ArrayList emptyCapacity = new ArrayList(0);
        System.out.println("new ArrayList(0) 初始化长度:" + getElementData(emptyCapacity));
        emptyCapacity.add(1);
        System.out.println("new ArrayList(0).add() 后长度:" + getElementData(emptyCapacity));
    }

    public static int getElementData(ArrayList arrayList) throws Exception {
        Class<? extends ArrayList> aClass = arrayList.getClass();
        Field field = aClass.getDeclaredField("elementData");
        field.setAccessible(true);
        Object[] o = (Object[]) field.get(arrayList);
        return o.length;
    }
}
/*
new ArrayList() 初始化长度:0
new ArrayList().add() 后长度:10
--- --- ---
new ArrayList(0) 初始化长度:0
new ArrayList(0).add() 后长度:1
 */

5)ArrayList 还提供了一个 public 的扩容方法进行扩容;

public void ensureCapacity(int minCapacity) {
    // 如果是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则 minExpand = 0,否则为 10
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        ? 0
        : DEFAULT_CAPACITY;

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

6)当一直往数组中添加元素,然后删除大百分比元素时,将会造成空间上的浪费,ArrayList 提供了一个相应的 public 方法来对容量进行回收。如下:

public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = (size == 0)
            ? EMPTY_ELEMENTDATA
            : Arrays.copyOf(elementData, size);
    }
}
ArrayList 中的迭代器

在 ArrayList 中,总共有 3 种迭代器:普通的迭代器、List 专有的迭代器 和 可分割的迭代器(并行遍历迭代器)。同时应该注意到,这些迭代器都是 fail-fast 的。

先来看一个通用的迭代器(通过 iterator() 获得):

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

/**
 * 内部类 ArrayList.Itr 是 AbstractList.Itr 的优化版本
 */
private class Itr implements Iterator<E> {
    int cursor;       // 要返回的下一个元素的索引
    int lastRet = -1; // 返回的最后一个元素的索引; 如果没有返回 -1
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification(); // 注意这个方法:fail-fast 的实现
        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();
    }

    // 迭代器 fail-fast 机制的实现:如果 modCount 不等于当前 expectedModCount 则意味着改实例被修改过
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

在上述迭代器中,注意到:如果在并发环境下,不能保证迭代器的快速失败行为(不能保证 modCount 和 expectedModCount 的一致性)。

再来看一个 ListIterator 迭代器(通过 listIterator() 获得):

public ListIterator<E> listIterator() {
    return new ListItr(0);
}
public ListIterator<E> listIterator(int index) {
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException("Index: "+index);
    return new ListItr(index);
}

/**
 * 内部类 ArrayList.ListItr 是 AbstractList.ListItr 的优化版本
 */
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();
        }
    }
}

相比于通用的迭代器,ListIterator 实现了双向移动、set 方法替换访问过的最近一个元素等操作。

最后,来看一个 Java 8 新增的 Spliterator 迭代器(通过 spliterator() 获得):

@Override
public Spliterator<E> spliterator() {
    return new ArrayListSpliterator<>(this, 0, -1, 0);
}

static final class ArrayListSpliterator<E> implements Spliterator<E> {

    private final ArrayList<E> list;
    private int index; // current index, modified on advance/split
    private int fence; // -1 until used; then one past last index
    private int expectedModCount; // initialized when fence set

    /** Create new spliterator covering the given  range */
    ArrayListSpliterator(ArrayList<E> list, int origin, int fence,
                         int expectedModCount) {
        this.list = list; // OK if null unless traversed
        this.index = origin;
        this.fence = fence;
        this.expectedModCount = expectedModCount;
    }

    private int getFence() { // initialize fence to size on first use
        int hi; // (a specialized variant appears in method forEach)
        ArrayList<E> lst;
        if ((hi = fence) < 0) {
            if ((lst = list) == null)
                hi = fence = 0;
            else {
                expectedModCount = lst.modCount;
                hi = fence = lst.size;
            }
        }
        return hi;
    }

    public ArrayListSpliterator<E> trySplit() {
        int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
        return (lo >= mid) ? null : // divide range in half unless too small
        new ArrayListSpliterator<E>(list, lo, index = mid,
                                    expectedModCount);
    }

    public boolean tryAdvance(Consumer<? super E> action) {
        if (action == null)
            throw new NullPointerException();
        int hi = getFence(), i = index;
        if (i < hi) {
            index = i + 1;
            @SuppressWarnings("unchecked") E e = (E)list.elementData[i];
            action.accept(e);
            if (list.modCount != expectedModCount)
                throw new ConcurrentModificationException();
            return true;
        }
        return false;
    }

    public void forEachRemaining(Consumer<? super E> action) {
        int i, hi, mc; // hoist accesses and checks from loop
        ArrayList<E> lst; Object[] a;
        if (action == null)
            throw new NullPointerException();
        if ((lst = list) != null && (a = lst.elementData) != null) {
            if ((hi = fence) < 0) {
                mc = lst.modCount;
                hi = lst.size;
            }
            else
                mc = expectedModCount;
            if ((i = index) >= 0 && (index = hi) <= a.length) {
                for (; i < hi; ++i) {
                    @SuppressWarnings("unchecked") E e = (E) a[i];
                    action.accept(e);
                }
                if (lst.modCount == mc)
                    return;
            }
        }
        throw new ConcurrentModificationException();
    }

    public long estimateSize() {
        return (long) (getFence() - index);
    }

    public int characteristics() {
        return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
    }
}
ArrayList 中的序列化与反序列化
/**
 * 序列化方法
 * 将 ArrayLisy 实例的状态保存到一个流里面
 */
private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    s.defaultWriteObject();

    // Write out size as capacity for behavioural compatibility with clone()
    s.writeInt(size);

    // 按照顺序写入所有的元素
    for (int i=0; i<size; i++) {
        s.writeObject(elementData[i]);
    }

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

/**
 * 反序列化方法
 * 根据一个流(参数)重新生成一个ArrayList
 */
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in capacity
    s.readInt();

    if (size > 0) {
        // be like clone(), allocate array based upon size not capacity
        ensureCapacityInternal(size);

        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            a[i] = s.readObject();
        }
    }
}

可以看到,对于 elementData 为什么使用 transient 修饰,是因为 JDK 不想将整个 elementData 都序列化或者反序列化,而只是将 size 和实际存储的元素序列化或反序列化,从而节省空间和时间。

ArrayList 中的视图

ArrayList 对 List 的视图提供了支持,可通过 subList 方法获取,在视图上的操作将会影响其实例本身。解析如下:

public List<E> subList(int fromIndex, int toIndex) {
    /* 范围校验 */
    subListRangeCheck(fromIndex, toIndex, size);
    /* 通过当前 this 实例创建一个 SubList 实例,也因此,对视图的修改通过 this 这个关系影响了原数据 */
    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;
    }

    public E set(int index, E e) {
        rangeCheck(index);
        checkForComodification();
        E oldValue = ArrayList.this.elementData(offset + index);
        ArrayList.this.elementData[offset + index] = e;
        return oldValue;
    }

    /* 省略了一些方法 */
}
  • SubList 的一些方法,是基于 elementData[] 的 index + 偏移位置 进行修改,是会直接修改 ArrayList 中 elementData 数组的,使用中应该注意;
  • SubList 没有实现 Serializable 接口,是不能序列化的
ArrayList 中的克隆

ArrayList 的 clone 操作是浅拷贝(元素本身不会被复制),实现如下:

public Object clone() {
    try {
        ArrayList<?> v = (ArrayList<?>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
}

小结

  • ArrayList 底层的数据结构是 Onject 数组
  • ArrayList 可以自动扩容,不传初始容量或者初始容量是 0,都会初始化一个 EMPTY_ELEMENTDATA 空数组,但是如果添加元素,会自动进行扩容,所以,创建 ArrayList 的时候,给初始容量是必要的
  • Arrays.asList() 方法返回的是的 Arrays内部的 ArrayList,并且这个 list 是不可修改的,用的时候需要注意
  • subList() 返回内部类,不能序列化,和 ArrayList 共用同一个数组
  • ArrayList 提供了多种迭代器,在使用迭代器遍历时,删除时要用迭代器支持的操作,迭代器的 remove 方法,或者可以用倒序的 for 循环
  • ArrayList 使用 transient 修饰 elementData[],重写了序列化、反序列化方法,避免序列化、反序列化全部数组,浪费时间和空间
  • elementData 不使用 private 修饰的原因是:non-private 可以简化内部类的访问
  • 对 ArrayList 的管理中,因底层是 Object 数组,使用了 Arrays 的 copyOf、sort 和 System 的 arraycopy 等工具类进行操作。
  1. 问:ArrayList 是线程不安全的,那又是如何解决 Spliterator 中多线程安全问题呢?

相关面经

通过这些练习更好的帮助吸收关于 ArrayList 的一些常见的、易混淆的知识点。

ArrayList 的总览

  1. 简单介绍下 ArrayList。

    • ArrayList 是可调整大小的数组实现;允许存放重复元素,元素可以为 null;实现不是同步的;提供了多种 fail-fst 迭代器;引入了泛型机制。
    • 与数组相比,不能存放基本类型的数据,有容量管理机制对数组大小进行扩容/回收。
    • 与 LinkedList 相比,查找和访问元素的速度较快,但新增,删除的速度较慢

    ArrayList就是动态数组,是Array的复杂版本,它提供了动态的增加和减少元素,实现了Collection和List接口,灵活的设置数组的大小等好处。

  2. 为什么 ArrayList 线程不安全还使用他呢?

    因为我们正常使用的场景中,都是用来查询,不会涉及太频繁的增删,如果涉及频繁的增删,可以使用LinkedList,如果你需要线程安全就使用Vector,这就是三者的区别了,实际开发过程中还是ArrayList使用最多的。

    不存在一个集合工具是查询效率又高,增删效率也高的,还线程安全的,至于为啥大家看代码就知道了,因为数据结构的特性就是优劣共存的,牺牲了性能,那就安全,牺牲了安全那就快速。

  3. ArrayList 是线程安全的么?

    当然不是,线程安全版本的数组容器是 Vector,Vector 的实现很简单,就是把所有的方法统统加上 synchronized 就完事了。

    也可以不使用 Vector,用 Collections.synchronizedList 把一个普通 ArrayList 包装成一个线程安全版本的数组容器也可以,原理同 Vector 是一样的,就是给所有的方法套上一层 synchronized。

  4. ArrayList 用来做队列合适么?

    ArrayList极不适合做队列。 队列一般是FIFO(先入先出)的,如果用ArrayList做队列,就需要在数组尾部追加数据,数组头部删除数组,反过来也可以。但是无论如何总会有一个操作会涉及到数组的数据搬迁,这个是比较耗费性能的。

    ArrayList 是很适合做堆栈的。

  5. 那数组适合用来做队列么?

    数组是非常合适的。 比如ArrayBlockingQueue内部实现就是一个环形队列,它是一个定长队列,内部是用一个定长数组来实现的。

    另外著名的 Disruptor 开源 Library 也是用环形数组来实现的超高性能队列,具体原理不做解释,比较复杂。

    简单点说就是使用两个偏移量来标记数组的读位置和写位置,如果超过长度就折回到数组开头,前提是它们是定长数组。

  6. ArrayList的遍历和LinkedList遍历性能比较如何?

    论遍历 ArrayList 要比 LinkedList 快得多,ArrayList 遍历最大的优势在于内存的连续性,CPU 的内部缓存结构会缓存连续的内存片段,可以大幅降低读取内存的性能开销。

ArrayList 结构相关

  1. ArrayList 都有哪些字段,有什么作用?

    elementData、size、继承基类的 modCount;

    和一些辅助字段:EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA、DEFAULT_CAPACITY、MAX_ARRAY_SIZE。

  2. Object[] EMPTY_ELEMENTDATA、DEFAULTCAPACITY_EMPTY_ELEMENTDATA 与 elementData 在哪里用到?

    • DEFAULTCAPACITY_EMPTY_ELEMENTDATA,仅在 使用 ArrayList() 时使用;

    • 当 ArrayList(int) 入参为 0 或者 ArrayList(Collection<? extends E>) 集合容量为空时,指定 EMPTY_ELEMENTDATA;

    • 而 elementData 则是实际存储元素的数组。

  3. ArrayList 的最大长度为多少?为什么 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ?

    这是有些虚拟机在数组中保留了一些标题字,尝试分配更大的数组可能导致 OutOfMemoryError:Requested array size exceeds VM limit

  4. 对于 elementData 为什么不是 private 的,为什么使用了 transient?

    • 对于 non-private 的 elementData:我们知道的,使用 private 修饰的变量,内部类也是可以访问到的。难道注释中 non-private to simplify nested class access(非私有,以简化嵌套类访问)的这句话有毛病?

      当我们看表面看不到什么东西的时候,不妨看一下底层。 private对InnerClass的影响.jpg private对InnerClass的影响2.jpg 虽然字节码指令我还看不太懂,但是我能品出来,注释是没毛病的,private修饰的确会影响内部类的访问。

    • 对于使用 transient 进行修饰 elementData:是因为 JDK 不想将整个 elementData 都序列化或者反序列化,而只是将 size 和实际存储的元素序列化或反序列化,从而节省空间和时间。

容量管理机制

  1. ArrayList 的底层是 Object[],但数组的大小是固定的,如果不断的往里面添加数据的话,不会有问题吗?是如何进行扩容的?

    ArrayList 通过构造器初始化的时候指定底层数组的大小,在添加元素时进行容量的扩充。 笔记 - ArrayList - 1 - 扩容.png

  2. 为什么 ArrayList 的默认数组大小为什么是 10?

    据说是因为 sun 的程序员对一系列广泛使用的程序代码进行了调研,结果就是 10 这个长度的数组是最常用的最有效率的。也有说就是随便起的一个数字,8 个 12 个都没什么区别,只是因为 10 这个数组比较的圆满而已。

  3. 问个真实的场景,ArrayList(int initialCapacity)会不会初始化数组大小?

    不会初始化数组大小!而且这里有一个经典的BUG:

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(10);
        System.out.println(list.size()); // 0
        list.set(1, 10); // 抛出:IndexOutOfBoundsException
    }
    

    这是因为使用 ArrayList(int initialCapacity) 的时候进行了容量分配(new Object[]),但是其 size 依旧为 0,在 set 时,根据 size 进行判断(此时 size == 0),下标越界,抛出索引越界异常。

    总结就是说,初始化的时候通过 capacity 申请数组,而不改变 size 的大小。

常用方法

  1. ArrayList 的增删很慢,你能说一下 ArrayList 在增删的时候是怎么做的么?主要说一下他为啥慢。

    • 每次操作之前都会进行扩容检测,如果进行扩容,那速度将会更慢;
    • 对于新增位置之后的元素都要进行复制。
  2. ArrayList插入删除一定慢么?

    这个取决于删除的元素离数组末端有多远,ArrayList拿来作为堆栈来用还是挺合适的,push 和 pop 操作完全不涉及数据移动操作。

经典 BUG

  1. 使用 Arrays.asList() 获取 List,然后 add 操作报错。

    这是因为 Arrays.asList 返回的是内部类 ArrayList,而这个内部类的数组类型为 private final E[] a; 所以在使用 add 操作时报错。

    public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
        System.out.println(list.getClass()); // class java.util.ArrayList
        System.out.println(list.toArray().getClass()); // class [Ljava.lang.Object;
    
        // 注意 toArray() 返回的类型
        List<String> asList = Arrays.asList("1", "2");
        System.out.println(asList.getClass()); // class java.util.Arrays$ArrayList 是 Arrays 的内部类 ArrayList
        System.out.println(asList.toArray().getClass()); //class [Ljava.lang.String;
        // asList.add("4"); // 异常:UnsupportedOperationException
    
        ArrayList<String> list1 = new ArrayList<>(asList);
        System.out.println(list1.getClass()); // class java.util.ArrayList
        System.out.println(list1.toArray().getClass()); // class [Ljava.lang.Object;
        list1.add("3"); // is OK
    }
    /*
    通过这个例子可以看出来,`java.util.ArrayList.toArray()`方法会返回`Object[]`没有问题。
    而`java.util.Arrays`的私有内部类ArrayList的`toArray()`方法可能不返回`Object[]`。
    
    通过 Arrays.asList() 源码不难看出,`java.util.Arrays`的内部ArrayList的`toArray()`方法,是构造方法接收什么类型的数组,就返回什么类型的数组。
    */
    
  2. 使用 new ArrayList(10) 创建 list,然后 set 操作,报错。

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(10);
        System.out.println(list.size()); // 0
        list.set(1, 10); // 抛出:IndexOutOfBoundsException
    }
    

    这是因为使用 ArrayList(int initialCapacity) 的时候进行了容量分配(new Object[]),但是其 size 依旧为 0,在 set 时,根据 size 进行判断(此时 size == 0),下标越界,抛出索引越界异常。

  3. 使用迭代器时抛出的 ConcurrentModificationException 异常。

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6));
        Iterator<Integer> itr = list.iterator();
        while (itr.hasNext()) {
            Integer i = itr.next();
            list.remove(i); /* 抛出 ConcurrentModificationException */
        }
    }
    

    在迭代的时候,会校验modCount是否等于expectedModCount。不管单线程还是多线程,只要不等于就会抛出著名的ConcurrentModificationException异常。

参考