1.概述
ArrayList其实可以理解为一个动态数组,是一个复杂的Array。与普通的数组相比,它仅能存储对象(普通的数组可以存储对象和基本类型的元素),而且它是动态的,动态数组的意思就是指底层的数组大小并不是固定的,而是根据添加的元素大小进行一个判断,不够的话就动态扩容。每个 ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝。因此,如果可预知数据量的多少,可在构造 ArrayList 时指定其容量。在添加大量元素前,应用程序也可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量,这可以减少递增式再分配的数量,从而提高性能。
应该注意的是,ArrayList的实现不是同步的。在多线程的环境下,如果有多个线程同时访问一个ArrayList,而其中至少有一个线程对该ArrayList的结构进行了修改,那么它必须保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)因此,ArrayList建议只在线程安全的环境下使用。在多线程的环境下,建议使用concurrent包下的CopyOnWriteArrayList,或者使用Collections包下的synchronizedList来把ArrayList包装起来。例如:
List<String> list = Collections.synchronizedList(new ArrayList<>());
2.实现原理
2.1 类的定义以及一些属性:
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[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; // non-private to simplify nested class access private int size;
在ArrayList这个类的定义中,我们可以看到,它是继承于AbstractList这个类的,
ArrayList实现了List接口,而List是一个数组队列,提供了相关的增、删、改、查等功能;
ArrayList实现了RandomAccess接口,即提供了随机访问功能。RandmoAccess 是 java 中用来被 List 实现,为 List 提供快速访问功能的。在 ArrayList 中,我们即可以通过元素的序号(相当于数组的下标)快速获取元素对象;这就是快速随机访问;
ArrayList实现了Cloneable接口,意味着它实现了clone()方法,即ArrayList能够被克隆;
ArrayList实现了 java.io.Serializable 接口,这意味着 ArrayList 支持序列化,能通过序列化去传输。
ArrayList的实现关系:
再来看一下ArrayList的成员变量:
elementData:用来存储ArrayList里面的元素;
size:用来表示ArrayList的大小,即ArrayList里面存储了多少对象;
DEFAULT_CAPACITY:这个貌似是JDK1.8中新增的,表示ArrayList的默认大小。
2.2 ArrayList的构造函数:
/** * 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(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); } } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ 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; } }
根据每个构造函数的注释即可看到:
public ArrayList():构造一个初始容量为10的空列表;
public ArrayList(int initialCapacity):构造一个指定初始容量的空列表;
public ArrayList(Collection<? extends E> c):构造一个包含指定 collection 的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
2.3 ArrayList的增删改查
1)ArrayList的增
①:
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { //调用ensureCapacityInternal(int minCapacity)方法来判断当前容量是否足够大 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code //如果当前容量已经大于数组长度,则调用grow(int minCapacity)方法进行扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ 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); }
add(E e):这个方法是将元素加入到ArrayList的尾部,在添加元素之前,会检查容器的容量,当容量不足时,会调用grow(int minCapacity)方法进行扩容。
②:
/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { //检查传入的下标是否越界 rangeCheckForAdd(index); //检查容量大小 ensureCapacityInternal(size + 1); // Increments modCount!! //index后面的元素后移一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
add(int index, E element):在指定下标插入元素。可以看到,在插入元素之前,先会检查传入的下标是否满足要求,然后再检查容量是否足够。当以上这两个条件都已经满足时,会调用System.arraycopy()方法,将index后面的元素都后移一位,然后再在index插入新的元素。
③:
/** * Appends all of the elements in the specified collection to the end of * this list, in the order that they are returned by the * specified collection's Iterator. The behavior of this operation is * undefined if the specified collection is modified while the operation * is in progress. (This implies that the behavior of this call is * undefined if the specified collection is this list, and this * list is nonempty.) * * @param c collection containing elements to be added to this list * @return <tt>true</tt> if this list changed as a result of the call * @throws NullPointerException if the specified collection is null */ 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; } /** * Inserts all of the elements in the specified collection into this * list, starting at the specified position. Shifts the element * currently at that position (if any) and any subsequent elements to * the right (increases their indices). The new elements will appear * in the list in the order that they are returned by the * specified collection's iterator. * * @param index index at which to insert the first element from the * specified collection * @param c collection containing elements to be added to this list * @return <tt>true</tt> if this list changed as a result of the call * @throws IndexOutOfBoundsException {@inheritDoc} * @throws NullPointerException if the specified collection is null */ public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
addAll(Collection<? extends E> c) 和 addAll(int index, Collection<? extends E> c):将特定 Collection 中的元素添加到 Arraylist 中,和前面的两种插入方式大同小异。
在ArrayList中,add()方法的本质是在特定的位置插入新的元素,但其中也会涉及到数组容量不够而动态增长的问题。
2)ArrayList的删
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { //检查传入的下标是否大于数组的大小 rangeCheck(index); modCount++; E oldValue = elementData(index); //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 remove method that skips bounds checking and does not * return the value removed. */ 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 } /** * Checks if the given index is in range. If not, throws an appropriate * runtime exception. This method does *not* check if the index is * negative: It is always used immediately prior to an array access, * which throws an ArrayIndexOutOfBoundsException if index is negative. */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
remove(int index):删除特定位置上的元素,在传入index时,会检查是否越界,如果在正常范围内,则把index后面的元素往前移一位
remove(Object o):删除指定的元素:如果要删除的对象为null,则遍历ArrayList,找到第一个为null的元素并删除;如果元素不为null,则遍历元素,找到第一个与o相同的元素并删除(ArrayList中可以包含null并且允许有重复的值)
删除元素的方法也很简单,把指定元素后面的元素分别往前移一位,并且将size-1位置上的元素置为null即可。
3)ArrayList的改
/** * Replaces the element at the specified position in this list with * the specified element. * * @param index index of the element to replace * @param element element to be stored at the specified position * @return the element previously at the specified position * @throws IndexOutOfBoundsException {@inheritDoc} */ public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
set(int index, E element):该方法首先调用rangeCheck(index);来校验 index 变量是否超出数组范围,超出则抛出异常。而后,取出原 index 位置的值,并且将新的 element 放入 Index 位置,返回 oldValue。
4)ArrayList的查
/** * Returns the index of the first occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the lowest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. */ public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; } /** * Returns the index of the last occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the highest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. */ public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } /** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E get(int index) { rangeCheck(index); return elementData(index); }
indexOf(Object o):查找ArrayList中是否包含此元素,如果包含,则返回此元素第一次出现的下标,否则返回-1;
lastIndexOf(Object o):这个方法与上面的方法不同的是,如果ArrayList包含要查找的元素,返回它最后一次出现的位置,否则返回-1;
get(int index):这个方法较为简单。首先判断传入的下标是否越界,然后返回数组中下标值为index的元素即可。
2.4 ArrayList的扩容
/** * Increases the capacity of this <tt>ArrayList</tt> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ 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); } } private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ 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); } 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里面添加元素的时候已经见到过扩容的代码。每一次添加元素,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。而我们有两个方法进行扩容:一种是开发者自己调用ensureCapacity(int minCapacity)这个方法来主动进行扩容;另外一种方法是,当在往ArrayList里面添加元素的过程中,数组容量不足,那么它自身会调用grow(int minCapacity)方法来进行扩容,以满足容量的需求。
我们在上面的源码中可以看到,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的 1.5 倍(从int newCapacity = oldCapacity + (oldCapacity >> 1);)
这行代码得出)。每一次的扩容,都要将数据拷贝到新数组中,这样无疑会大大降低性能。因此,我们在使用ArrayList的时候,应尽量避免对其进行扩容。(如果能够事先估计元素的数量时,最好在初始化的时候指定其容量)
2.4 Fail-Fast机制
ArrayList也采用了fail-fast机制。在多线程的环境中,面对并发的修改时,迭代器会很快失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
fail-fast机制在HashMap的使用及其实现原理有过介绍,这里就不再重复了^_^