一. 初始化
1.1 底层的数据结构以及容量
由源码可知,ArrayList其实是一个Object的数组,初始化的时候如果不指定容量的话,那么初始话的容量就是10
/*** Default initial capacity.*/private static final int DEFAULT_CAPACITY = 10;/*** The array buffer into which the elements of the ArrayList are stored.* The capacity of the ArrayList is the length of this array buffer. Any* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA* will be expanded to DEFAULT_CAPACITY when the first element is added.*/transient Object[] elementData; // non-private to simplify nested class access
1.2 初始化的几种方法
1.2.1 不指定长度
不指定长度的话,会使用无参构造函数,并且让存储数据的elementData数组,指向一个已经声明空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA
/*** Shared empty array instance used for empty instances.*/private static final Object[] EMPTY_ELEMENTDATA = {};/*** Shared empty array instance used for default sized empty instances. We* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when* first element is added.*/private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** Constructs an empty list with an initial capacity of ten.*/public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
1.2.2 指定长度
若指定长度的话,那么会new出一个新的Object的数组,如果指定的长度为0,那么elementData就会指向一个已经声明的空数组EMPTY_ELEMENTDATA。如果指定的长度是无效的话,比如小于0,那么就会抛出IllegalArgumentException
/*** Shared empty array instance used for empty instances.*/private static final Object[] EMPTY_ELEMENTDATA = {};/*** 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);}}
1.2.3 指定一个Collection的子类
- 给定一个Collection的子类,来new出一个ArrayList,首先判断给定的子类示例大小,如果等于0,则直接让elementData指向EMPTY_ELEMENTDATA。
- 如果给定的子类是ArrayList的类型的,那么直接改变elementData引用的指向,让其指向给定的示例的内存地址即可。
如果如果给定的子类不是ArrayList的类型的,那么调用Arrays.copyOf(a, size, Object[].class);方法进行复制操作
/*** Shared empty array instance used for empty instances.*/private static final Object[] 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) {Object[] a = c.toArray();if ((size = a.length) != 0) {if (c.getClass() == ArrayList.class) {elementData = a;} else {elementData = Arrays.copyOf(a, size, Object[].class);}} else {// replace with empty array.elementData = EMPTY_ELEMENTDATA;}}
二. 添加元素
2.1 添加元素以及扩容机制(Java1.8)
2.1.1 add方法
添加一个元素到arrayList中,会先调用ensureCapacityInternal(size + 1);方法,进行容量确认,是否需要扩容。然后在索引size+1的位置上,把元素插入到数组里面
/*** 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;}
2.1.2 ensureCapacityInternal方法
注意,此方法接收的参数是 size + 1。
此方法又调用了 calculateCapacity(elementData, minCapacity) 方法来计算新的容量,并且调用了ensureExplicitCapacity来完成扩容
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}
2.1.3 calculateCapacity方法
此方法接受参数 当前数组和 minCapacity(其等于 size + 1)。如果当前是空数组的话,那么就把默认的容量10和最小的指定容量minCapacity的较大的一个值返回去,此时因为是空数组,所以一定会返回10.
如果不是空数组的话,那么就会返回minCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;}
2.1.4 ensureExplicitCapacity方法
用这个方法来判断,是否需要扩容。
- 当我们要 add 进第 1 个元素到 ArrayList 时,elementData.length 为 0 (因为还是一个空的 list),因为执行了 ensureCapacityInternal() 方法 ,所以 minCapacity 此时为 10。此时,minCapacity - elementData.length > 0成立,所以会进入 grow(minCapacity) 方法。
- 当 add 第 2 个元素时,minCapacity 为 2,此时 e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,minCapacity - elementData.length > 0 不成立,所以不会进入 (执行)grow(minCapacity) 方法。
- 添加第 3、4···到第 10 个元素时,依然不会执行 grow 方法,数组容量都为 10。
直到添加第 11 个元素,minCapacity(为 11)比 elementData.length(为 10)要大。进入 grow 方法进行扩容。
PS:element.length指的是数组的长度,就算有的索引没数据,这个索引的位置也是会被算进长度里面的。
size这个变量值得是,这个arrayList示例里面有多少个有效元素。注意不要和上面的length搞混了
private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity);}
2.1.5 grow方法
- 当 add 第 1 个元素时,oldCapacity 为 0,经比较后第一个 if 判断成立,newCapacity = minCapacity(为 10)。但是第二个 if 判断不会成立,即 newCapacity 不比 MAX_ARRAY_SIZE 大,则不会进入 hugeCapacity 方法。数组容量为 10,add 方法中 return true,size 增为 1。
- 当 add 第 11 个元素进入 grow 方法时,newCapacity 为 15,比 minCapacity(为 11)大,第一个 if 判断不成立。新容量没有大于数组最大 size,不会进入 hugeCapacity 方法。数组容量扩为 15,add 方法中 return true,size 增为 11。
- 以此类推······
- elementData = Arrays.copyOf(elementData, newCapacity); 来把元素copy到指定长度的数组中,再返回给elementData
int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)! 奇偶不同,比如 :10+10/2 = 15, 33+33/2=49。如果是奇数的话会丢掉小数.
“>>”(移位运算符):>>1 右移一位相当于除 2,右移 n 位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了 1 位所以相当于 oldCapacity /2。对于大数据的 2 进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源
/*** 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;/*** 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 codeint 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);}
2.1.6 hugeCapacity方法
从上面 grow() 方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) hugeCapacity() 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果 minCapacity 大于最大容量,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 Integer.MAX_VALUE - 8。
/*** 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) // overflowthrow new OutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;}
2.2 在指定位置插入元素
2.2.1 add方法
方法接收,插入位置的索引和,被插入的元素。在插入之前会进行索引有效性的check。然后判断是否扩容,扩容的方法参照2.1中内容。
需要重点说明一下System.arraycopy这个方法。
最后把元素插入到对应的索引位置就可以了。
/*** 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!!System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;size++;}
2.2.2 System.arraycopy方法
没用的注释有一大堆,所以删掉了一些。5个参数的意思分别是
- 源数组:要被拷贝的数组是哪个
- 源数组开始位置:要哪个位置开始拷贝
- 目标数组:要拷贝到哪个数组
- 目标数组开始位置:要从哪个位置开始被拷贝
- 拷贝多少个元素
2.2.1中的调用 System.arraycopy(elementData, index, elementData, index + 1,size - index);
例子: 有一个数组[1, 2, 3, 4, 5], 要在索引2的位置插入8. 那么调用此方法就是
把[1, 2, 3, 4, 5]从索引2的位置开始拷贝,要拷贝3个元素(size-index即5-2) 即拷贝了[3, 4, 5], 到目标数组中(也就是源数组)的索引(index+1)的位置,即索引3。那么现在的数组就变成了[1, 2, 3, 3, 4, 5]
最后调用的elementData[index] = element; 指令把数组里面红色的3覆盖成8就大功告成。
/*** @param src the source array.* @param srcPos starting position in the source array.* @param dest the destination array.* @param destPos starting position in the destination data.* @param length the number of array elements to be copied.*/public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
