1、首先看ArrayList默认构造方法创建
/**
* 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;
}
从以上两段代码得知,List集合底层就是用数组(属性名:elementData)存储数据的,默认构造方法初始化数组为空,那么数组的长度即为0,所以通过默认构造方法创建集合,默认数组的长度为0
2、接着看集合add方法和addAll方法
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
/**
* 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;
}
/**
* 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;
}
add方法:调用ensureCapacityInternal,传入参数:size+1(size为集合大小(初始化默认也为0),即数组中存有数据的长度(并不是数组的长度)
addAll方法:首先将传入集合转换成数组,然后调用ensureCapacityInternal,传入参数为:size+传入数组的长度。
3、接着看ensureCapacityInternal方法:
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
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);
}
判断elementData与DEFAULTCAPACITY_EMPTY_ELEMENTDATA这地址是否是相等的,而第一次调用add方法这两个值肯定是相等的,而DEFAULT_CAPACITY=10,minCapacity=1,所以执行minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity)得出minCapacity =10。最终得出这个方法的目的就是判断如果是第一次执行add方法,最小容量(minCapacity,可以理解为至少需要用来存数据的长度)设置为10,以后的每次进来都是size+1。
接着看调用ensureExplicitCapacity:modCount记录操作数组的次数,然后看if方法,此时判断最小容量如果大于数组长度则调用grow方法进行扩容,当第一次执行add由于elementData.length=0,所以执行grow方法参数为10
4、grow为扩容核心代码
/**
* 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 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;
}
首先:新容量(注意:新容量是基于当前数组长度进行扩容)扩大到原容量(当前数组长度)的1.5倍,右移一位相当于原数值除以2。
接着:if (newCapacity - minCapacity < 0)判断新容量的大小是否小于最小容量(也就是当前数组长度扩容后还是小于最小容量,就将最小容量直接设置为新容量,否则将扩容后的容量设置为新容量,而从add方法进来正常情况不会为真(除非扩容后的容量大于了int最大值,成负数了),从addAll方法基本可能为真(当前数组的长度加上准备添加数组的长度大于扩容后的容量)),所以第一次执行add方法肯定是跳过的。
接着:if (newCapacity - MAX_ARRAY_SIZE > 0)判断新容量如果大于MAX_ARRAY_SIZE 则进行容量最大值控制,传入最小容量执行hugeCapacity方法。
如果minCapacity为负数则抛出内存溢出的异常,可是什么情况下为负数呢?,我们继续看,当最小容量超过了int最大值,就会成负数,而什么情况下会超过最大值?我这里想到了一种情况,就是在执行addAll的情况下,此时的最小容量是当前数组的长度加上准备添加数组的长度。最后返回,如果最小容量大于MAX_ARRAY_SIZE 则返回integer最大值(这里有一个疑问,文档最后描述),否则,就是MAX_ARRAY_SIZE,从而就限制了数组的最大长度。
然后执行grow方法的elementData = Arrays.copyOf(elementData, newCapacity);重新生成扩容后的数组对象。
最后回到初始add与addAll方法
add方法:接着执行elementData[size++] = e;将数据直接添加在可能扩容了也可能看没扩容后的数组中,然后size++;
addAll方法:接着执行System.arraycopy(a, 0, elementData, size, numNew);将集合数据添加在可能扩容了也可能看没扩容后的数组中,然后 size += numNew;
最后得出结论:jdk1.8在用默认构造方法创建对象的时候,数组长度为0,在第一次执行add才会将数组长度设置为10,或执行addAll方法的时候,如果添加集合长度大于10则数组长度为集合长度,否则数组长度也为10,所以一般创建集合的时候,提前给集合设置大概所需用到的容量大小(这个容量大小是指数组的长度,并不是集合的size),这样就可以在集合创建后减少执行add或addAll方法时的扩容操作,从而提升代码执行效率。
最后说一下那个疑问:
首先MAX_ARRAY_SIZE值(数组elementData的最大长度)为什么是Integer.MAX_VALUE - 8,因为数组在java里是一种特殊类型,既不是基本数据类型,也不是引用数据类型。有别于普通的“类的实例”对象,java里数组不是类,所以也就没有对应的class文件,数组类型是由jvm从元素类型合成出来的,在jvm中获取数组的长度是用字节码指令的获取的。在数组的对象头里有一个_length字段,记录数组长度,只需要去读_length字段就可以了。所以MAX_ARRAY_SIZE值为Integer最大值减8,这个8就是就是存了数组_length字段。
好,我们已经明白了为什么数组最大长度要减8了,不过这个时候又有了一个新的疑问:
就是从如果最小容量大于MAX_ARRAY_SIZE 则返回integer最大值,而数组长度不是不能为int最大值吗?这个不是和矛盾了吗?
这个疑问我也没有解开,可能是因为判断容量最大限制无需很严谨,因为一个集合这么大本身就影响效率,而且就算真的大过头了,反正也就是抛出一个内存溢出的异常,同理minCapacity<0情况的解决方式。