关注点 | 结论 ---|--- ArrayList是否允许空 | 允许 ArrayList是否允许重复数据 | 允许 ArrayList是否有序 | 有序 ArrayList是否线程安全 | 非线程安全 #### 1. 继承的父类和实现的接口 ```java public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable ``` > ArrayList 继承了 AbstractList 类,此类提供了 List 接口的骨干实现,继承此类的子类适合用于“随机访问”数据存储(如数组),Vector 也是此类的子类。 > 与 AbstractList 类对应的类是 AbstractSequentialList 类,继承该类的子类适合用于“连续访问”数据存储(如链接列表),代表的子类如 LinkedList 。 > ArrayList 实现了 List 接口,List 接口通常表示一个列表(数组、队列、链表、栈等),其中的元素可以重复,代表的实现类有 ArrayList、LinkedList、Stack、Vector。 > ArrayList 实现了 RandomAccess 接口,该接口为标记接口,用来表明其支持快速随机访问。 > ArrayList 实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。 > ArrayList 实现了 Serializable 接口,因此它支持序列化,能够通过序列化传输。 #### 2. 属性 ```java /** * 序列版本号 */ private static final long serialVersionUID = 8683452581122892189L; /** * 默认容量 */ private static final int DEFAULT_CAPACITY = 10; /** * 无参的空数组常量 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 有参的空数组 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 存放元素的数组,从这可以发现ArrayList的底层实现就是一个Object数组 */ transient Object[] elementData; /** * 当前的实际元素个数 */ private int size; ``` #### 3. 构造函数 ```java //用初始容量作为参数的构造方法 public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //初始容量大于0,实例化数组 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //初始容量等于0,赋予空数组 this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } //无参的构造方法 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } ``` > 从构造方法中我们可以看见,默认情况下,elementData是一个大小为0的空数组,当我们指定了初始大小的时候,elementData的初始大小就变成了我们所指定的初始大小了。 #### 4. get,set方法 ```java //返回指定索引的值 public E get(int index) { //检查索引值是否越界 rangeCheck(index); return elementData(index); } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; } //将制定索引上的值替换为新值,并返回旧值 public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } ``` > 因为ArrayList是采用数组结构来存储的,所以它的get方法非常简单,先是判断一下有没有越界,之后就可以直接通过数组下标来获取元素了,所以get的时间复杂度是O(1)。 > set方法的作用是把下标为index的元素替换成element,跟get非常类似,所以就不在赘述了,时间复杂度度为O(1)。 #### 5. add ```java //将指定的元素添加到列表的尾部 public boolean add(E e) { //动态数组 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //默认是扩大为原来的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); //1.5倍还是不够的话,直接用传进来的minCapacity作为容量值 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //自己传进来的值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; } //将元素添加到指定位置 public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! //arraycopy(被复制的数组,从第几个元素开始复制,要复制到的数组,从第几个元素开始粘贴,一共需要复制的元素个数) System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } ``` > ArrayList的add方法也很好理解,在插入元素之前,它会先检查是否需要扩容,然后再把元素添加到数组中最后一个元素的后面。在ensureCapacityInternal方法中,我们可以看见,如果当elementData为空数组时,它会使用默认的大小去扩容。所以说,通过无参构造方法来创建ArrayList时,它的大小其实是为0的,只有在使用到的时候,才会通过grow方法去创建一个大小为10的数组。 > 第一个add方法的复杂度为O(1),虽然有时候会涉及到扩容的操作,但是扩容的次数是非常少的,所以这一部分的时间可以忽略不计。如果使用的是带指定下标的add方法,则复杂度为O(n),因为涉及到对数组中元素的移动,这一操作是非常耗时的 > grow方法是在数组进行扩容的时候用到的,从中我们可以看见,ArrayList每次扩容都是扩1.5倍,然后调用Arrays类的copyOf方法,把元素重新拷贝到一个新的数组中去。 #### 6. remove ```java public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) //arraycopy(被复制的数组,从第几个元素开始复制,要复制到的数组,从第几个元素开始粘贴,一共需要复制的元素个数) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; } ``` > remove方法与add带指定下标的方法非常类似,也是调用系统的arraycopy方法来移动元素,时间复杂度为O(n)。 #### 7. size(),isEmpty() ```java public int size() { return size; } public boolean isEmpty() { return size == 0; } ``` > size方法非常简单,它是直接返回size的值,也就是返回数组中元素的个数,时间复杂度为O(1)。这里要注意一下,返回的并不是数组的实际大小。 #### 8.indexOf()和lastIndexOf() ```java 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; } 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; } ``` > indexOf方法的作用是返回第一个等于给定元素的值的下标。它是通过遍历比较数组中每个元素的值来查找的,所以它的时间复杂度是O(n)。 > lastIndexOf的原理跟indexOf一样,而它仅仅是从后往前找起罢了。 #### 9. removeRange() ```java //删除fromIndex到toIndex之间的全部元素 protected void removeRange(int fromIndex, int toIndex) { modCount++; //numMoved为删除索引后面的元素个数 int numMoved = size - toIndex; //将删除索引后面的元素复制到以fromIndex为起始位置的存储空间中去 System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved); int newSize = size - (toIndex-fromIndex); //将ArrayList后面(toIndex-fromIndex)个元素置为null for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; } ``` #### 10. Vector > vector因为很多方法都跟ArrayList一样,只是多加了个synchronized来保证线程安全罢了。如果照着ArrayList的方式再将一次就显得没意思了,所以只把Vector与ArrayList的不同点提一下就可以了。 Vector比ArrayList多了一个属性: > protected int capacityIncrement; 这个属性是在扩容的时候用到的,它表示每次扩容只扩capacityIncrement个空间就足够了。该属性可以通过构造方法给它赋值。先来看一下构造方法: ```java public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; } public Vector(int initialCapacity) { this(initialCapacity, 0); } public Vector() { this(10); } ``` 从构造方法中,我们可以看出Vector的默认大小也是10,而且它在初始化的时候就已经创建了数组了,这点跟ArrayList不一样。再来看一下grow方法: ```java private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } ``` 从grow方法中我们可以发现,newCapacity默认情况下是两倍的oldCapacity,而当指定了capacityIncrement的值之后,newCapacity变成了oldCapacity+capacityIncrement。 #### 10. 总结 > 注意扩充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后用Arrays.copyof()方法将元素拷贝到新的数组(详见下面的第3点)。从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则建议使用LinkedList。 > 1、ArrayList创建时的大小为0;当加入第一个元素时,进行第一次扩容时,默认容量大小为10。 > 2、ArrayList每次扩容都以当前数组大小的1.5倍去扩容。 > 3、Vector创建时的默认大小为10。 > 4、Vector每次扩容都以当前数组大小的2倍去扩容。当指定了capacityIncrement之后,每次扩容仅在原先基础上增加capacityIncrement个单位空间。 > 5、ArrayList和Vector的add、get、size方法的复杂度都为O(1),remove方法的复杂度为O(n)。 > 6、ArrayList是非线程安全的,Vector是线程安全的。 > 7. ArrayList不是线程同步的,在多线程下可以考虑使用 Collections.synchronizedList 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问:代码如下: ```java List list = Collections.synchronizedList(new ArrayList(...)); ```