java集合框架之ArrayList
废话不多说直接进入正题,本文将一步步来分解ArrayList:
先看下ArrayList主要的成员变量:
/** * Default initial capacity. 默认初始化容量10 */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
再看其构造方法,其主要操作为初始化元素数组elementData:
初始化不指定初始容量:elementData初始化为默认的空数组
初始化指定初始容量:做一个简单的判断然后初始化elementData的length为指定大小
初始化传入集合: elementData为 Arrays.copyOf 方法返回的数组(最终调用的还是 System.arraycopy 方法)
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) { // 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的扩容过程,我们直接定位到其 add 方法上:
两个add方法都涉及到扩容操作,只不过第二个方法多了参数检查的操作,可以直接定位到 ensureCapacityInternal 方法上
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++; }
接下来我们看看 ensureCapacityInternal 方法到底做了些什么操作,到底如何扩容的:
最终从 grow 方法中可知每次扩容增加1/2
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 static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 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); }
set、get 方法直接通过数组下标操作,而且add(E e)则是直接加入到数组末尾,也是通过下标,所以这三个方法的性能都非常不错
public E get(int index) { rangeCheck(index); return elementData(index); } public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
而remove(index)、remove(object)和add(index,element)在移除和添加进去的时候都调用了 System.arraycopy 方法来移动数组这就导致了在数据量非常大的时候这几个方法的性能低下
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++; } 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 }
小结: ArrayList通过数组实现,内部通过System.arraycopy实现数组的扩容,基于数组顺序存储的特性,直接通过下标获取元素的效率高,所以ArrayList的get、set方法效率高,同时我们在遍历ArrayList时 fori 的遍历效率是要高于迭代器的。
jdk除了提供ArrayList外还提供了Vector这个线程安全的容器,其大部分方法都进行了同步,在扩容时直接增加一倍,可见ArrayList的空间利用率要优于Vector。