JDK1.8.131版本。java
首先查看ArrayList实例化方法相关代码。数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; private static final Object[] EMPTY_ELEMENTDATA = {}; private int size; 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); } } 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; } }
默认无参构造方法。elementData实际存放数据元素,从这些能够看出,ArrayList底层是用数组来实现,元素是连续的;这也体现了ArrayList有序,读取快,固然数组维护角标,若是在ArrayList元素里面随机删除个元素,就比较慢了。由于须要维护角标,因此被删除元素后面元素的角标都须要-1。app
咱们知道ArrayList底层是使用数组来实现的,可是数组是不可变;ArrayList是怎么实现,动态改变数据元素的呢?这个经过示例(查看源码)来讲明问题以及缘由。dom
ArrayList<String> list = new ArrayList<String>(); list.add("a");
使用ArrayList的无参构造方法,初始化list集合,并使用add方法添加一个"a"元素。ide
首先,ArrayList先将内部的 elementData 初始化(elementData = {}),这个时候数组的长度是0,list集合的长度也是0.测试
而后,add方法添加元素,方法中先执行ensureCapacityInternal(size + 1)方法。this
add方法以及其涉及到的方法:code
/** * 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(size + 1); // Increments modCount!! elementData[size++] = e; return true; } /** * Increments modCount!! */ 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); } /** * 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); } /** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 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将元素添加到集合中以前,先执行了ensureCapacityInternal方法,执行这个方法的目的是检查集合中元素(数组)是否是还能再插入一个元素,不能则扩容。ci
流程以下:element
先检查elementData数组是不是默认初始化的数组,若是是则选择默认初始化容量和最小容量数值大的那一个,再将数值赋值给minCapacity变量;若是elementData.lengh小于minCapacity,则扩容(grow).每次扩容后的容量为int newCapacity = oldCapacity + (oldCapacity >> 1);(以前容量的1.5倍)。若是newCapacity(扩容后的容量)还小于(minCapacity),则直接将新容量定义为minCapacity;若是newCapacity大于了MAX_ARRAY_SIZE,则比较minCapacity和MAX_ARRAY_SIZE的大小,大于则返回Integer.MAX_VALUE,不然返回MAX_ARRAY_SIZE,到此扩容后的数组长度肯定了,接着使用Arrays.copyOf方法,将以前元素,放入新的数组中,最后执行add方法中elementData[size++] = e,将元素放到集合中。至此添加元素流程完成。
自顶一个ZArrayList,来模拟ArrayList。
public class ZArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; transient Object[] elementData; private static final Object[] EMPTY_ELEMENTDATA = {}; private static final int DEFAULT_CAPACITY = 10; private int size; public ZArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ZArrayList(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 ZArrayList(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 { this.elementData = EMPTY_ELEMENTDATA; } } public boolean add(E e) { ensureCapacityInternal(size + 1); 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++; if (minCapacity - elementData.length > 0) grow(minCapacity); } private int count = 0; private void grow(int minCapacity) { count +=1; int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } public int getCount() { return count; } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } @Override public E get(int index) { return null; } @Override public int size() { return 0; } }
首先定义全局变量int count = 0,在扩容方法grow中,使用count+=1,这样能大体得出扩容次数。测试代码
@Test public void testCountList(){ ZArrayList<Integer> zl = new ZArrayList<>(); System.out.println("初始化:"+zl.getCount()+"\t内部数组长度:"+zl.elementData.length); for (int a=0;a< 1000000;a++){ zl.add(a); log(zl, a); } System.out.println("---------------------"); for (int b = 1000000; b>0;b--){ zl.remove(zl.get(0)); log(zl, b); } } private void log(ZArrayList<Integer> zl, int b) { if (b == 1){ System.out.println("1次:\t"+zl.getCount()+"\t内部数组长度:"+zl.elementData.length); } else if (b==100){ System.out.println("100次:\t"+zl.getCount()+"\t内部数组长度:"+zl.elementData.length); } else if (b == 1000){ System.out.println("1000次:\t"+zl.getCount()+"\t内部数组长度:"+zl.elementData.length); } else if (b == 10000){ System.out.println("1万次:\t"+zl.getCount()+"\t内部数组长度:"+zl.elementData.length); } else if (b == 100000){ System.out.println("10万次:\t"+zl.getCount()+"\t内部数组长度:"+zl.elementData.length); } else if (b == 1000000){ System.out.println("100万次:\t"+zl.getCount()+"\t内部数组长度:"+zl.elementData.length); } }
结果以下:
初始化:0 内部数组长度:0 1次: 1 内部数组长度:10 100次: 7 内部数组长度:109 1000次: 13 内部数组长度:1234 1万次: 19 内部数组长度:14053 10万次: 24 内部数组长度:106710 --------------------- 100万次: 30 内部数组长度:1215487 10万次: 30 内部数组长度:1215487 1万次: 30 内部数组长度:1215487 1000次: 30 内部数组长度:1215487 100次: 30 内部数组长度:1215487 1次: 30 内部数组长度:1215487
从这些数据能够得出若是依次添加100个元素,大概会扩容7次,若是将数据依次删除,可是list内部数组的长度不变。
特别注意:ArrayList内部数组Object[] elementData的长度不等于ArrayList的size,经过源码能够看到在add/addAll/remove/removeAll中都维护了一个size变量,这个才是ArrayList元素的个数。
从上边的分析中能够得出,若是添加数据量比较大的话,最好是给ArrayList一个默认的初始容量,用以减小扩容的次数。
以此类推ArrayList的其余方法addAll和remove方法,原理是类似的,再也不赘述。